X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/55e303ae13a4cf49d70f2294092726f2fffb9ef2..2d21ac55c334faf3a56e5634905ed6987fc787d4:/bsd/kern/kern_audit.c diff --git a/bsd/kern/kern_audit.c b/bsd/kern/kern_audit.c index ce838a4f4..df9f61d2d 100644 --- a/bsd/kern/kern_audit.c +++ b/bsd/kern/kern_audit.c @@ -1,16 +1,19 @@ /* - * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2003-2007 Apple Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ - * - * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER @@ -20,34 +23,71 @@ * Please see the License for the specific language governing rights and * limitations under the License. * - * @APPLE_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * NOTICE: This file was modified by McAfee Research in 2004 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. */ #include #include #include #include #include -#include +#include +#include #include #include #include #include #include #include -#include -#include -#include +#include +#include #include -#include -#include #include #include #include - +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include #include #include +#include + +#if CONFIG_MACF +#include +#include +#include +#include +#define MAC_ARG_PREFIX "arg: " +#define MAC_ARG_PREFIX_LEN 5 +#endif + +#include + +#include +#include -#ifdef AUDIT +#if AUDIT /* * The AUDIT_EXCESSIVELY_VERBOSE define enables a number of @@ -57,8 +97,10 @@ */ /* #define AUDIT_EXCESSIVELY_VERBOSE */ #ifdef AUDIT_EXCESSIVELY_VERBOSE +#define AUDIT_PRINTF_ONLY #define AUDIT_PRINTF(x) printf x #else +#define AUDIT_PRINTF_ONLY __unused #define AUDIT_PRINTF(X) #endif @@ -67,7 +109,7 @@ #undef assert() #endif #define assert(cond) \ - ((void) ((cond) ? 0 : panic("%s:%d (%s)", __FILE__, __LINE__, # cond))) + ((void) ((cond) ? 0 : panic("Assert failed: %s", # cond))) #else #include #endif /* DIAGNOSTIC */ @@ -82,20 +124,42 @@ int audit_suspended; * Mutex to protect global variables shared between various threads and * processes. */ -static mutex_t *audit_mtx; +static lck_grp_t *audit_grp; +static lck_attr_t *audit_attr; +static lck_grp_attr_t *audit_grp_attr; +static lck_mtx_t *audit_mtx; /* * Queue of audit records ready for delivery to disk. We insert new - * records at the tail, and remove records from the head. + * records at the tail, and remove records from the head. Also, + * a count of the number of records used for checking queue depth. + * In addition, a counter of records that we have allocated but are + * not yet in the queue, which is needed to estimate the total + * size of the combined set of records outstanding in the system. */ static TAILQ_HEAD(, kaudit_record) audit_q; +static size_t audit_q_len; +static size_t audit_pre_q_len; + +static wait_queue_t audit_wait_queue; +static zone_t audit_zone; +#if CONFIG_MACF +static zone_t audit_mac_label_zone; +#endif /* * Condition variable to signal to the worker that it has work to do: * either new records are in the queue, or a log replacement is taking * place. */ -static wait_queue_t audit_wait_queue; +static int audit_worker_event; +#define AUDIT_WORKER_EVENT ((event_t)&audit_worker_event) + +/* + * The audit worker thread (which is lazy started when we first + * rotate the audit log. + */ +static thread_t audit_worker_thread = THREAD_NULL; /* * When an audit log is rotated, the actual rotation must be performed @@ -111,17 +175,61 @@ static wait_queue_t audit_wait_queue; * by the worker thread so a waiting thread can start another replacement. * We also store a credential to perform audit log write operations with. */ -static wait_queue_t audit_replacement_wait_queue; +static int audit_replacement_event; +#define AUDIT_REPLACEMENT_EVENT ((event_t)&audit_replacement_event) static int audit_replacement_flag; static struct vnode *audit_replacement_vp; -static struct ucred *audit_replacement_cred; +static kauth_cred_t audit_replacement_cred; + +/* + * Wait queue for auditing threads that cannot commit the audit + * record at the present time. Also, the queue control parameter + * structure. + */ +static int audit_commit_event; +#define AUDIT_COMMIT_EVENT ((event_t)&audit_commit_event) + +static struct au_qctrl audit_qctrl; /* * Flags to use on audit files when opening and closing. */ -const static int audit_open_flags = FWRITE | O_APPEND; -const static int audit_close_flags = FWRITE | O_APPEND; +static const int audit_open_flags = FWRITE | O_APPEND; +static const int audit_close_flags = FWRITE | O_APPEND; + +/* + * Global audit statistiscs. + */ +static struct audit_fstat audit_fstat; + +/* + Preselection mask for non-attributable events. + */ +static struct au_mask audit_nae_mask; + +/* + * Flags related to Kernel->user-space communication. + */ +static int audit_file_rotate_wait; + +/* + * Flags controlling behavior in low storage situations. + * Should we panic if a write fails? Should we fail stop + * if we're out of disk space? Are we currently "failing + * stop" due to out of disk space? + */ +static int audit_panic_on_write_fail; +static int audit_fail_stop; +static int audit_in_failure; + +/* + * When in a fail-stop mode, threads will drop into this wait queue + * rather than perform auditable events. They won't ever get woken + * up. + */ +static int audit_failure_event; +#define AUDIT_FAILURE_EVENT ((event_t)&audit_failure_event) /* * XXX: Couldn't find the include file for this, so copied kern_exec.c's @@ -129,36 +237,175 @@ const static int audit_close_flags = FWRITE | O_APPEND; */ extern task_t kernel_task; +extern zone_t mac_audit_data_zone; static void audit_free(struct kaudit_record *ar) { if (ar->k_ar.ar_arg_upath1 != NULL) { - kmem_free(kernel_map, ar->k_ar.ar_arg_upath1, MAXPATHLEN); + kfree(ar->k_ar.ar_arg_upath1, MAXPATHLEN); } if (ar->k_ar.ar_arg_upath2 != NULL) { - kmem_free(kernel_map, ar->k_ar.ar_arg_upath2, MAXPATHLEN); + kfree(ar->k_ar.ar_arg_upath2, MAXPATHLEN); + } if (ar->k_ar.ar_arg_kpath1 != NULL) { - kmem_free(kernel_map, ar->k_ar.ar_arg_kpath1, MAXPATHLEN); + kfree(ar->k_ar.ar_arg_kpath1, MAXPATHLEN); + } if (ar->k_ar.ar_arg_kpath2 != NULL) { - kmem_free(kernel_map, ar->k_ar.ar_arg_kpath2, MAXPATHLEN); + kfree(ar->k_ar.ar_arg_kpath2, MAXPATHLEN); + } if (ar->k_ar.ar_arg_text != NULL) { - kmem_free(kernel_map, ar->k_ar.ar_arg_text, MAXPATHLEN); + kfree(ar->k_ar.ar_arg_text, MAXPATHLEN); + } if (ar->k_udata != NULL) { - kmem_free(kernel_map, ar->k_udata, ar->k_ulen); + kfree(ar->k_udata, ar->k_ulen); + } + +#if CONFIG_MACF + if (ar->k_ar.ar_vnode1_mac_labels != NULL) { + zfree(audit_mac_label_zone, ar->k_ar.ar_vnode1_mac_labels); + } + if (ar->k_ar.ar_vnode2_mac_labels != NULL) { + zfree(audit_mac_label_zone, ar->k_ar.ar_vnode2_mac_labels); + } + if (ar->k_ar.ar_cred_mac_labels != NULL) { + zfree(audit_mac_label_zone, ar->k_ar.ar_cred_mac_labels); } - kmem_free(kernel_map, ar, sizeof(*ar)); + if (ar->k_ar.ar_arg_mac_string != NULL) { + kfree(ar->k_ar.ar_arg_mac_string, + MAC_MAX_LABEL_BUF_LEN + MAC_ARG_PREFIX_LEN); + } + + /* Free the audit data from the MAC policies. */ + do { + struct mac_audit_record *head, *next; + + head = LIST_FIRST(ar->k_ar.ar_mac_records); + while (head != NULL) { + next = LIST_NEXT(head, records); + zfree(mac_audit_data_zone, head->data); + kfree(head, sizeof(*head)); + head = next; + } + + kfree(ar->k_ar.ar_mac_records, + sizeof(*ar->k_ar.ar_mac_records)); + } while (0); +#endif + + zfree(audit_zone, ar); } +/* + * Converts an audit record into the BSM format before writing out to the + * audit logfile. Will perform it's own vnode iocounting. + * + * Returns: + * -1 if it could not get an ioreference on the vnode. + * EINVAL if the kaudit_record ar is not a valid audit record. + */ static int -audit_write(struct vnode *vp, struct kaudit_record *ar, struct ucred *cred, - struct proc *p) +audit_write(struct vnode *vp, struct kaudit_record *ar, vfs_context_t ctx) { - int ret; + struct vfsstatfs *mnt_stat = &vp->v_mount->mnt_vfsstat; + int ret = 0; struct au_record *bsm; + off_t file_size; + + mach_port_t audit_port; + + if (vnode_getwithref(vp)) + return ENOENT; + + /* + * First, gather statistics on the audit log file and file system + * so that we know how we're doing on space. In both cases, + * if we're unable to perform the operation, we drop the record + * and return. However, this is arguably an assertion failure. + */ + ret = vfs_update_vfsstat(vp->v_mount, ctx, VFS_KERNEL_EVENT); + if (ret) + goto out; + + /* update the global stats struct */ + if ((ret = vnode_size(vp, &file_size, ctx)) != 0) + goto out; + audit_fstat.af_currsz = file_size; + + /* + * Send a message to the audit daemon when disk space is getting + * low. + * XXX Need to decide what to do if the trigger to the audit daemon + * fails. + */ + if(host_get_audit_control_port(host_priv_self(), &audit_port) + != KERN_SUCCESS) + printf("Cannot get audit control port\n"); + + if (audit_port != MACH_PORT_NULL) { + uint64_t temp; + + /* + * If we fall below percent free blocks, then trigger the + * audit daemon to do something about it. + */ + if (audit_qctrl.aq_minfree != 0) { + temp = mnt_stat->f_blocks / (100 / audit_qctrl.aq_minfree); + if (mnt_stat->f_bfree < temp) { + ret = audit_triggers(audit_port, + AUDIT_TRIGGER_LOW_SPACE); + if (ret != KERN_SUCCESS) { + printf( + "Failed audit_triggers(AUDIT_TRIGGER_LOW_SPACE): %d\n", ret); + /* + * XXX: What to do here? Disable auditing? + * panic? + */ + } + } + } + /* Check if the current log file is full; if so, call for + * a log rotate. This is not an exact comparison; we may + * write some records over the limit. If that's not + * acceptable, then add a fudge factor here. + */ + if ((audit_fstat.af_filesz != 0) && + (audit_file_rotate_wait == 0) && + (file_size >= (off_t)audit_fstat.af_filesz)) { + audit_file_rotate_wait = 1; + ret = audit_triggers(audit_port, + AUDIT_TRIGGER_FILE_FULL); + if (ret != KERN_SUCCESS) { + printf( + "Failed audit_triggers(AUDIT_TRIGGER_FILE_FULL): %d\n", ret); + /* XXX what to do here? */ + } + } + } + + /* + * If the estimated amount of audit data in the audit event queue + * (plus records allocated but not yet queued) has reached the + * amount of free space on the disk, then we need to go into an + * audit fail stop state, in which we do not permit the + * allocation/committing of any new audit records. We continue to + * process packets but don't allow any activities that might + * generate new records. In the future, we might want to detect + * when space is available again and allow operation to continue, + * but this behavior is sufficient to meet fail stop requirements + * in CAPP. + */ + if (audit_fail_stop && + (unsigned long) + ((audit_q_len + audit_pre_q_len + 1) * MAX_AUDIT_RECORD_SIZE) / + mnt_stat->f_bsize >= (unsigned long)(mnt_stat->f_bfree)) { + printf( + "audit_worker: free space below size of audit queue, failing stop\n"); + audit_in_failure = 1; + } /* * If there is a user audit record attached to the kernel record, @@ -170,69 +417,88 @@ audit_write(struct vnode *vp, struct kaudit_record *ar, struct ucred *cred, * user record? For now, we write the user record first, and * we ignore errors. */ - if (ar->k_udata != NULL) { - vn_rdwr(UIO_WRITE, vp, (void *)ar->k_udata, ar->k_ulen, - (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, p); + if (ar->k_ar_commit & AR_COMMIT_USER) { + ret = vn_rdwr(UIO_WRITE, vp, (void *)ar->k_udata, ar->k_ulen, + (off_t)0, UIO_SYSSPACE32, IO_APPEND|IO_UNIT, vfs_context_ucred(ctx), NULL, vfs_context_proc(ctx)); + if (ret) + goto out; } /* * Convert the internal kernel record to BSM format and write it * out if everything's OK. */ + if (!(ar->k_ar_commit & AR_COMMIT_KERNEL)) { + ret = 0; + goto out; + } + ret = kaudit_to_bsm(ar, &bsm); - if (ret == BSM_NOAUDIT) - return (0); + if (ret == BSM_NOAUDIT) { + ret = 0; + goto out; + } + /* + * XXX: We drop the record on BSM conversion failure, but really + * this is an assertion failure. + */ if (ret == BSM_FAILURE) { AUDIT_PRINTF(("BSM conversion failure\n")); - return (-1); + ret = EINVAL; + goto out; } - /* XXX This function can be called with the kernel funnel held, - * which is not optimal. We should break the write functionality + /* XXX: We should break the write functionality * away from the BSM record generation and have the BSM generation * done before this function is called. This function will then * take the BSM record as a parameter. */ ret = (vn_rdwr(UIO_WRITE, vp, (void *)bsm->data, bsm->len, - (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, p)); - + (off_t)0, UIO_SYSSPACE32, IO_APPEND|IO_UNIT, vfs_context_ucred(ctx), NULL, vfs_context_proc(ctx))); kau_free(bsm); +out: + /* + * When we're done processing the current record, we have to + * check to see if we're in a failure mode, and if so, whether + * this was the last record left to be drained. If we're done + * draining, then we fsync the vnode and panic. + */ + if (audit_in_failure && + audit_q_len == 0 && audit_pre_q_len == 0) { + (void)VNOP_FSYNC(vp, MNT_WAIT, ctx); + panic("Audit store overflow; record queue drained."); + } + + vnode_put(vp); return (ret); } static void -audit_worker() +audit_worker(void) { - int do_replacement_signal, error, release_funnel; + int do_replacement_signal, error; TAILQ_HEAD(, kaudit_record) ar_worklist; - struct kaudit_record *ar, *ar_start, *ar_stop; + struct kaudit_record *ar; struct vnode *audit_vp, *old_vp; - struct ucred *audit_cred, *old_cred; - struct proc *audit_p; + kauth_cred_t audit_cred; + proc_t audit_p; AUDIT_PRINTF(("audit_worker starting\n")); TAILQ_INIT(&ar_worklist); - audit_cred = NULL; + audit_cred = NOCRED; audit_p = current_proc(); audit_vp = NULL; - /* - * XXX: Presumably we can assume Mach threads are started without - * holding the BSD kernel funnel? - */ - thread_funnel_set(kernel_flock, FALSE); - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); while (1) { + struct vfs_context context; + /* * First priority: replace the audit log target if requested. - * As we actually close the vnode in the worker thread, we - * need to grab the funnel, which means releasing audit_mtx. - * In case another replacement was scheduled while the mutex - * we released, we loop. * * XXX It could well be we should drain existing records * first to ensure that the timestamps and ordering @@ -240,41 +506,35 @@ audit_worker() */ do_replacement_signal = 0; while (audit_replacement_flag != 0) { - old_cred = audit_cred; + kauth_cred_t old_cred = audit_cred; + old_vp = audit_vp; audit_cred = audit_replacement_cred; audit_vp = audit_replacement_vp; - audit_replacement_cred = NULL; + audit_replacement_cred = NOCRED; audit_replacement_vp = NULL; audit_replacement_flag = 0; audit_enabled = (audit_vp != NULL); - if (old_vp != NULL || audit_vp != NULL) { - mutex_unlock(audit_mtx); - thread_funnel_set(kernel_flock, TRUE); - release_funnel = 1; - } else - release_funnel = 0; /* * XXX: What to do about write failures here? */ if (old_vp != NULL) { - AUDIT_PRINTF(("Closing old audit file\n")); - vn_close(old_vp, audit_close_flags, old_cred, - audit_p); - crfree(old_cred); - old_cred = NULL; + AUDIT_PRINTF(("Closing old audit file vnode %p\n", old_vp)); + if (vnode_get(old_vp) == 0) { + vn_close(old_vp, audit_close_flags, vfs_context_kernel()); + vnode_put(old_vp); + AUDIT_PRINTF(("Audit file closed\n")); + } + else + printf("audit_worker(): Couldn't close audit file.\n"); + kauth_cred_unref(&old_cred); old_vp = NULL; - AUDIT_PRINTF(("Audit file closed\n")); } if (audit_vp != NULL) { AUDIT_PRINTF(("Opening new audit file\n")); } - if (release_funnel) { - thread_funnel_set(kernel_flock, FALSE); - mutex_lock(audit_mtx); - } do_replacement_signal = 1; } /* @@ -286,8 +546,8 @@ audit_worker() * successfully. */ if (do_replacement_signal) - wait_queue_wakeup_all(audit_replacement_wait_queue, - 0, THREAD_AWAKENED); + wait_queue_wakeup_all(audit_wait_queue, + AUDIT_REPLACEMENT_EVENT, THREAD_AWAKENED); /* * Next, check to see if we have any records to drain into @@ -297,9 +557,11 @@ audit_worker() int ret; AUDIT_PRINTF(("audit_worker waiting\n")); - ret = wait_queue_assert_wait(audit_wait_queue, 0, - THREAD_UNINT); - mutex_unlock(audit_mtx); + ret = wait_queue_assert_wait(audit_wait_queue, + AUDIT_WORKER_EVENT, + THREAD_UNINT, + 0); + lck_mtx_unlock(audit_mtx); assert(ret == THREAD_WAITING); ret = thread_block(THREAD_CONTINUE_NULL); @@ -308,7 +570,7 @@ audit_worker() AUDIT_PRINTF(("audit_worker: new vp = %p; value of flag %d\n", audit_replacement_vp, audit_replacement_flag)); - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); continue; } @@ -328,14 +590,21 @@ audit_worker() if (audit_vp == NULL) { while ((ar = TAILQ_FIRST(&audit_q))) { TAILQ_REMOVE(&audit_q, ar, k_q); + audit_q_len--; + if (audit_q_len <= audit_qctrl.aq_lowater) + wait_queue_wakeup_one( + audit_wait_queue, + AUDIT_COMMIT_EVENT, + THREAD_AWAKENED); + TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); } - mutex_unlock(audit_mtx); + lck_mtx_unlock(audit_mtx); while ((ar = TAILQ_FIRST(&ar_worklist))) { TAILQ_REMOVE(&ar_worklist, ar, k_q); audit_free(ar); } - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); continue; } @@ -353,10 +622,17 @@ audit_worker() */ while ((ar = TAILQ_FIRST(&audit_q))) { TAILQ_REMOVE(&audit_q, ar, k_q); + audit_q_len--; + if (audit_q_len <= audit_qctrl.aq_lowater) { + wait_queue_wakeup_one(audit_wait_queue, + AUDIT_COMMIT_EVENT, THREAD_AWAKENED); + } + TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); } - mutex_unlock(audit_mtx); - release_funnel = 0; + lck_mtx_unlock(audit_mtx); + context.vc_thread = current_thread(); + context.vc_ucred = audit_cred; while ((ar = TAILQ_FIRST(&ar_worklist))) { TAILQ_REMOVE(&ar_worklist, ar, k_q); if (audit_vp != NULL) { @@ -364,58 +640,66 @@ audit_worker() * XXX: What should happen if there's a write * error here? */ - if (!release_funnel) { - thread_funnel_set(kernel_flock, TRUE); - release_funnel = 1; - } - VOP_LEASE(audit_vp, audit_p, audit_cred, - LEASE_WRITE); - error = audit_write(audit_vp, ar, audit_cred, - audit_p); - if (error) + error = audit_write(audit_vp, ar, &context); + if (error && audit_panic_on_write_fail) { + panic("audit_worker: write error %d\n", + error); + } else if (error) { printf("audit_worker: write error %d\n", error); + } } audit_free(ar); } - if (release_funnel) - thread_funnel_set(kernel_flock, FALSE); - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); } } void audit_init(void) { - - /* Verify that the syscall to audit event table is the same - * size as the system call table. - */ - if (nsys_au_event != nsysent) { - printf("Security auditing service initialization failed, "); - printf("audit event table doesn't match syscall table.\n"); - return; - } - printf("Security auditing service present\n"); TAILQ_INIT(&audit_q); + audit_q_len = 0; audit_enabled = 0; audit_suspended = 0; audit_replacement_cred = NULL; audit_replacement_flag = 0; + audit_file_rotate_wait = 0; audit_replacement_vp = NULL; - audit_mtx = mutex_alloc(ETAP_NO_TRACE); + audit_fstat.af_filesz = 0; /* '0' means unset, unbounded */ + audit_fstat.af_currsz = 0; + audit_qctrl.aq_hiwater = AQ_HIWATER; + audit_qctrl.aq_lowater = AQ_LOWATER; + audit_qctrl.aq_bufsz = AQ_BUFSZ; + audit_qctrl.aq_minfree = AU_FS_MINFREE; + + audit_grp_attr = lck_grp_attr_alloc_init(); + audit_grp = lck_grp_alloc_init("audit", audit_grp_attr); + audit_attr = lck_attr_alloc_init(); + audit_mtx = lck_mtx_alloc_init(audit_grp, audit_attr); + audit_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO); - audit_replacement_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO); + audit_zone = zinit(sizeof(struct kaudit_record), + AQ_HIWATER*sizeof(struct kaudit_record), + 8192, + "audit_zone"); +#if CONFIG_MACF + /* Assume 3 MAC labels for each audit record: two for vnodes, + * one for creds. + */ + audit_mac_label_zone = zinit(MAC_AUDIT_LABEL_LEN, + AQ_HIWATER * 3*MAC_AUDIT_LABEL_LEN, + 8192, + "audit_mac_label_zone"); +#endif /* Initialize the BSM audit subsystem. */ kau_init(); - - kernel_thread(kernel_task, audit_worker); } static void -audit_rotate_vnode(struct ucred *cred, struct vnode *vp) +audit_rotate_vnode(kauth_cred_t cred, struct vnode *vp) { int ret; @@ -423,14 +707,16 @@ audit_rotate_vnode(struct ucred *cred, struct vnode *vp) * If other parallel log replacements have been requested, we wait * until they've finished before continuing. */ - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); while (audit_replacement_flag != 0) { AUDIT_PRINTF(("audit_rotate_vnode: sleeping to wait for " "flag\n")); - ret = wait_queue_assert_wait(audit_replacement_wait_queue, 0, - THREAD_UNINT); - mutex_unlock(audit_mtx); + ret = wait_queue_assert_wait(audit_wait_queue, + AUDIT_REPLACEMENT_EVENT, + THREAD_UNINT, + 0); + lck_mtx_unlock(audit_mtx); assert(ret == THREAD_WAITING); ret = thread_block(THREAD_CONTINUE_NULL); @@ -438,17 +724,23 @@ audit_rotate_vnode(struct ucred *cred, struct vnode *vp) AUDIT_PRINTF(("audit_rotate_vnode: woken up (flag %d)\n", audit_replacement_flag)); - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); } audit_replacement_cred = cred; audit_replacement_flag = 1; audit_replacement_vp = vp; /* - * Wake up the audit worker to perform the exchange once we - * release the mutex. + * Start or wake up the audit worker to perform the exchange. + * It will have to wait until we release the mutex. */ - wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED); + if (audit_worker_thread == THREAD_NULL) + audit_worker_thread = kernel_thread(kernel_task, + audit_worker); + else + wait_queue_wakeup_one(audit_wait_queue, + AUDIT_WORKER_EVENT, + THREAD_AWAKENED); /* * Wait for the audit_worker to broadcast that a replacement has @@ -457,15 +749,19 @@ audit_rotate_vnode(struct ucred *cred, struct vnode *vp) */ AUDIT_PRINTF(("audit_rotate_vnode: waiting for news of " "replacement\n")); - ret = wait_queue_assert_wait(audit_replacement_wait_queue, 0, - THREAD_UNINT); - mutex_unlock(audit_mtx); + ret = wait_queue_assert_wait(audit_wait_queue, + AUDIT_REPLACEMENT_EVENT, + THREAD_UNINT, + 0); + lck_mtx_unlock(audit_mtx); assert(ret == THREAD_WAITING); ret = thread_block(THREAD_CONTINUE_NULL); assert(ret == THREAD_AWAKENED); AUDIT_PRINTF(("audit_rotate_vnode: change acknowledged by " "audit_worker (flag " "now %d)\n", audit_replacement_flag)); + + audit_file_rotate_wait = 0; /* We can now request another rotation */ } /* @@ -474,21 +770,19 @@ audit_rotate_vnode(struct ucred *cred, struct vnode *vp) void audit_shutdown(void) { - - audit_rotate_vnode(NULL, NULL); + if (audit_mtx) + audit_rotate_vnode(NULL, NULL); } static __inline__ struct uthread * curuthread(void) { - - return (get_bsdthread_info(current_act())); + return (get_bsdthread_info(current_thread())); } static __inline__ struct kaudit_record * currecord(void) { - return (curuthread()->uu_ar); } @@ -504,42 +798,61 @@ currecord(void) * work, since we pre-select only based on the AUE_audit event type, * not the event type submitted as part of the user audit data. */ -struct audit_args { - void * record; - int length; -}; /* ARGSUSED */ int -audit(struct proc *p, struct audit_args *uap, register_t *retval) +audit(proc_t p, struct audit_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; int error; void * rec; struct kaudit_record *ar; + struct uthread *uthr; + + error = suser(kauth_cred_get(), &p->p_acflag); + if (error) + return (error); + + lck_mtx_lock(audit_mtx); + if ((uap->length <= 0) || (uap->length > (int)audit_qctrl.aq_bufsz)) { + lck_mtx_unlock(audit_mtx); + return (EINVAL); + } + lck_mtx_unlock(audit_mtx); ar = currecord(); - /* XXX: What's the proper error code if a user audit record can't - * be written due to auditing off, or otherwise unavailable? + /* If there's no current audit record (audit() itself not audited) + * commit the user audit record. */ - if (ar == NULL) + if (ar == NULL) { + uthr = curuthread(); + if (uthr == NULL) /* can this happen? */ return (ENOTSUP); - error = suser(pc->pc_ucred, &p->p_acflag); - if (error) - return (error); + /* This is not very efficient; we're required to allocate + * a complete kernel audit record just so the user record + * can tag along. + */ + uthr->uu_ar = audit_new(AUE_NULL, p, uthr); + if (uthr->uu_ar == NULL) /* auditing not on, or memory error */ + return (ENOTSUP); + ar = uthr->uu_ar; + } if (uap->length > MAX_AUDIT_RECORD_SIZE) return (EINVAL); - error = kmem_alloc(kernel_map, (vm_offset_t *)&rec, uap->length); - if (error != KERN_SUCCESS) - return(ENOMEM); + rec = (void *)kalloc((vm_size_t)uap->length); error = copyin(uap->record, rec, uap->length); if (error) goto free_out; +#if CONFIG_MACF + error = mac_system_check_audit(kauth_cred_get(), rec, uap->length); + if (error) + goto free_out; +#endif + /* Verify the record */ if (bsm_rec_verify(rec) == 0) { error = EINVAL; @@ -551,178 +864,513 @@ audit(struct proc *p, struct audit_args *uap, register_t *retval) * record along with the record for this audit event. */ ar->k_udata = rec; + ar->k_ar_commit |= AR_COMMIT_USER; ar->k_ulen = uap->length; return (0); free_out: - kmem_free(kernel_map, (vm_offset_t)rec, uap->length); + /* audit_syscall_exit() will free the audit record on the thread + * even if we allocated it above. + */ + kfree(rec, uap->length); return (error); } /* * System call to manipulate auditing. */ -struct auditon_args { - int cmd; - void * data; - int length; -}; /* ARGSUSED */ int -auditon(struct proc *p, struct auditon_args *uap, register_t *retval) +auditon(proc_t p, struct auditon_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; - int error; + int ret; + int len; + union auditon_udata udata; + proc_t tp = PROC_NULL; + kauth_cred_t my_cred; + + AUDIT_ARG(cmd, uap->cmd); + ret = suser(kauth_cred_get(), &p->p_acflag); + if (ret) + return (ret); + +#if CONFIG_MACF + ret = mac_system_check_auditon(kauth_cred_get(), uap->cmd); + if (ret) + return (ret); +#endif - error = suser(pc->pc_ucred, &p->p_acflag); - if (error) - return (error); - return (ENOSYS); -} + len = uap->length; + if ((len <= 0) || (len > (int)sizeof(union auditon_udata))) + return (EINVAL); -/* - * System call to pass in file descriptor for audit log. - */ -struct auditsvc_args { - int fd; - int limit; -}; -/* ARGSUSED */ -int -auditsvc(struct proc *p, struct auditsvc_args *uap, register_t *retval) -{ - register struct pcred *pc = p->p_cred; - int error; + memset((void *)&udata, 0, sizeof(udata)); + + switch (uap->cmd) { + /* Some of the GET commands use the arguments too */ + case A_SETPOLICY: + case A_SETKMASK: + case A_SETQCTRL: + case A_SETSTAT: + case A_SETUMASK: + case A_SETSMASK: + case A_SETCOND: + case A_SETCLASS: + case A_SETPMASK: + case A_SETFSIZE: + case A_SETKAUDIT: + case A_GETCLASS: + case A_GETPINFO: + case A_GETPINFO_ADDR: + ret = copyin(uap->data, (void *)&udata, uap->length); + if (ret) + return (ret); + AUDIT_ARG(auditon, &udata); + break; + } - error = suser(pc->pc_ucred, &p->p_acflag); - if (error) - return (error); - return (ENOSYS); + /* XXX Need to implement these commands by accessing the global + * values associated with the commands. + */ + lck_mtx_lock(audit_mtx); + switch (uap->cmd) { + case A_GETPOLICY: + if (!audit_fail_stop) + udata.au_policy |= AUDIT_CNT; + if (audit_panic_on_write_fail) + udata.au_policy |= AUDIT_AHLT; + break; + case A_SETPOLICY: + if (udata.au_policy & ~(AUDIT_CNT|AUDIT_AHLT)) { + ret = EINVAL; + break; + } + /* + * XXX - Need to wake up waiters if the policy relaxes? + */ + audit_fail_stop = ((udata.au_policy & AUDIT_CNT) == 0); + audit_panic_on_write_fail = (udata.au_policy & AUDIT_AHLT); + break; + case A_GETKMASK: + udata.au_mask = audit_nae_mask; + break; + case A_SETKMASK: + audit_nae_mask = udata.au_mask; + break; + case A_GETQCTRL: + udata.au_qctrl = audit_qctrl; + break; + case A_SETQCTRL: + if ((udata.au_qctrl.aq_hiwater > AQ_MAXHIGH) || + (udata.au_qctrl.aq_lowater >= udata.au_qctrl.aq_hiwater) || + (udata.au_qctrl.aq_bufsz > AQ_MAXBUFSZ) || + (udata.au_qctrl.aq_minfree < 0) || + (udata.au_qctrl.aq_minfree > 100)) { + ret = EINVAL; + break; + } + + audit_qctrl = udata.au_qctrl; + /* XXX The queue delay value isn't used with the kernel. */ + audit_qctrl.aq_delay = -1; + break; + case A_GETCWD: + ret = ENOSYS; + break; + case A_GETCAR: + ret = ENOSYS; + break; + case A_GETSTAT: + ret = ENOSYS; + break; + case A_SETSTAT: + ret = ENOSYS; + break; + case A_SETUMASK: + ret = ENOSYS; + break; + case A_SETSMASK: + ret = ENOSYS; + break; + case A_GETCOND: + if (audit_enabled && !audit_suspended) + udata.au_cond = AUC_AUDITING; + else + udata.au_cond = AUC_NOAUDIT; + break; + case A_SETCOND: + if (udata.au_cond == AUC_NOAUDIT) + audit_suspended = 1; + if (udata.au_cond == AUC_AUDITING) + audit_suspended = 0; + if (udata.au_cond == AUC_DISABLED) { + audit_suspended = 1; + audit_shutdown(); + } + break; + case A_GETCLASS: + udata.au_evclass.ec_class = + au_event_class(udata.au_evclass.ec_number); + break; + case A_SETCLASS: + au_evclassmap_insert(udata.au_evclass.ec_number, + udata.au_evclass.ec_class); + break; + case A_GETPINFO: + if (udata.au_aupinfo.ap_pid < 1) { + ret = EINVAL; + break; + } + if ((tp = proc_find(udata.au_aupinfo.ap_pid)) == NULL) { + ret = EINVAL; + break; + } + + lck_mtx_unlock(audit_mtx); + my_cred = kauth_cred_proc_ref(tp); + + udata.au_aupinfo.ap_auid = my_cred->cr_au.ai_auid; + udata.au_aupinfo.ap_mask.am_success = + my_cred->cr_au.ai_mask.am_success; + udata.au_aupinfo.ap_mask.am_failure = + my_cred->cr_au.ai_mask.am_failure; + udata.au_aupinfo.ap_termid.machine = + my_cred->cr_au.ai_termid.machine; + udata.au_aupinfo.ap_termid.port = + my_cred->cr_au.ai_termid.port; + udata.au_aupinfo.ap_asid = my_cred->cr_au.ai_asid; + + kauth_cred_unref(&my_cred); + + proc_rele(tp); + tp = PROC_NULL; + lck_mtx_lock(audit_mtx); + break; + case A_SETPMASK: + if (udata.au_aupinfo.ap_pid < 1) { + ret = EINVAL; + break; + } + if ((tp = proc_find(udata.au_aupinfo.ap_pid)) == NULL) { + ret = EINVAL; + break; + } + + /* + * we are modifying the audit info in a credential so we need a new + * credential (or take another reference on an existing credential that + * matches our new one). We must do this because the audit info in the + * credential is used as part of our hash key. Get current credential + * in the target process and take a reference while we muck with it. + */ + lck_mtx_unlock(audit_mtx); + for (;;) { + kauth_cred_t my_new_cred; + struct auditinfo temp_auditinfo; + + my_cred = kauth_cred_proc_ref(tp); + /* + * Set the credential with new info. If there is no + * change, we get back the same credential we passed + * in; if there is a change, we drop the reference on + * the credential we passed in. The subsequent + * compare is safe, because it is a pointer compare + * rather than a contents compare. + */ + temp_auditinfo = my_cred->cr_au; + temp_auditinfo.ai_mask.am_success = + udata.au_aupinfo.ap_mask.am_success; + temp_auditinfo.ai_mask.am_failure = + udata.au_aupinfo.ap_mask.am_failure; + my_new_cred = kauth_cred_setauditinfo(my_cred, &temp_auditinfo); + + if (my_cred != my_new_cred) { + proc_lock(tp); + /* need to protect for a race where another thread also changed + * the credential after we took our reference. If p_ucred has + * changed then we should restart this again with the new cred. + */ + if (tp->p_ucred != my_cred) { + proc_unlock(tp); + kauth_cred_unref(&my_new_cred); + /* try again */ + continue; + } + tp->p_ucred = my_new_cred; + proc_unlock(tp); + } + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + break; + } + proc_rele(tp); + lck_mtx_lock(audit_mtx); + break; + case A_SETFSIZE: + if ((udata.au_fstat.af_filesz != 0) && + (udata.au_fstat.af_filesz < MIN_AUDIT_FILE_SIZE)) { + ret = EINVAL; + break; + } + audit_fstat.af_filesz = udata.au_fstat.af_filesz; + break; + case A_GETFSIZE: + udata.au_fstat.af_filesz = audit_fstat.af_filesz; + udata.au_fstat.af_currsz = audit_fstat.af_currsz; + break; + case A_GETPINFO_ADDR: + ret = ENOSYS; + break; + case A_GETKAUDIT: + ret = ENOSYS; + break; + case A_SETKAUDIT: + ret = ENOSYS; + break; + } + /* Copy data back to userspace for the GET comands */ + if (ret == 0) { + switch (uap->cmd) { + case A_GETPOLICY: + case A_GETKMASK: + case A_GETQCTRL: + case A_GETCWD: + case A_GETCAR: + case A_GETSTAT: + case A_GETCOND: + case A_GETCLASS: + case A_GETPINFO: + case A_GETFSIZE: + case A_GETPINFO_ADDR: + case A_GETKAUDIT: + ret = copyout((void *)&udata, uap->data, uap->length); + break; + } + } + + lck_mtx_unlock(audit_mtx); + return (ret); } /* * System calls to manage the user audit information. - * XXXAUDIT May need to lock the proc structure. */ -struct getauid_args { - au_id_t *auid; -}; /* ARGSUSED */ int -getauid(struct proc *p, struct getauid_args *uap, register_t *retval) +getauid(__unused proc_t p, struct getauid_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; int error; - error = suser(pc->pc_ucred, &p->p_acflag); +#if CONFIG_MACF + error = mac_proc_check_getauid(p); if (error) return (error); +#endif - error = copyout((void *)&p->p_au->ai_auid, (void *)uap->auid, - sizeof(*uap->auid)); + error = copyout((void *)&kauth_cred_get()->cr_au.ai_auid, + uap->auid, sizeof(au_id_t)); if (error) return (error); return (0); } -struct setauid_args { - au_id_t *auid; -}; /* ARGSUSED */ int -setauid(struct proc *p, struct setauid_args *uap, register_t *retval) +setauid(proc_t p, struct setauid_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; int error; + au_id_t temp_au_id; - error = suser(pc->pc_ucred, &p->p_acflag); + error = suser(kauth_cred_get(), &p->p_acflag); if (error) return (error); - error = copyin((void *)uap->auid, (void *)&p->p_au->ai_auid, - sizeof(p->p_au->ai_auid)); + error = copyin(uap->auid, + (void *)&temp_au_id, + sizeof(au_id_t)); + if (error) + return (error); +#if CONFIG_MACF + error = mac_proc_check_setauid(p, temp_au_id); if (error) return (error); +#endif + + /* + * we are modifying the audit info in a credential so we need a new + * credential (or take another reference on an existing credential that + * matches our new one). We must do this because the audit info in the + * credential is used as part of our hash key. Get current credential + * in the target process and take a reference while we muck with it. + */ + for (;;) { + kauth_cred_t my_cred, my_new_cred; + struct auditinfo temp_auditinfo; + + my_cred = kauth_cred_proc_ref(p); + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + temp_auditinfo = my_cred->cr_au; + temp_auditinfo.ai_auid = temp_au_id; + my_new_cred = kauth_cred_setauditinfo(my_cred, &temp_auditinfo); + + if (my_cred != my_new_cred) { + proc_lock(p); + /* need to protect for a race where another thread also changed + * the credential after we took our reference. If p_ucred has + * changed then we should restart this again with the new cred. + */ + if (p->p_ucred != my_cred) { + proc_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + continue; + } + p->p_ucred = my_new_cred; + proc_unlock(p); + } + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + break; + } + + /* propagate the change from the process to Mach task */ + set_security_token(p); - audit_arg_auid(p->p_au->ai_auid); + audit_arg_auid(kauth_cred_get()->cr_au.ai_auid); return (0); } /* * System calls to get and set process audit information. + * If the caller is privileged, they get the whole set of + * audit information. Otherwise, the real audit mask is + * filtered out - but the rest of the information is + * returned. */ -struct getaudit_args { - struct auditinfo *auditinfo; -}; /* ARGSUSED */ int -getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval) +getaudit(proc_t p, struct getaudit_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; + struct auditinfo ai; int error; - error = suser(pc->pc_ucred, &p->p_acflag); +#if CONFIG_MACF + error = mac_proc_check_getaudit(p); if (error) return (error); - error = copyout((void *)p->p_au, (void *)uap->auditinfo, - sizeof(*uap->auditinfo)); +#endif + + ai = kauth_cred_get()->cr_au; + + /* only superuser gets to see the real mask */ + error = suser(kauth_cred_get(), &p->p_acflag); + if (error) { + ai.ai_mask.am_success = ~0; + ai.ai_mask.am_failure = ~0; + } + + error = copyout(&ai, uap->auditinfo, sizeof(ai)); if (error) return (error); return (0); } -struct setaudit_args { - struct auditinfo *auditinfo; -}; /* ARGSUSED */ int -setaudit(struct proc *p, struct setaudit_args *uap, register_t *retval) +setaudit(proc_t p, struct setaudit_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; int error; + struct auditinfo temp_auditinfo; + kauth_cred_t safecred; - error = suser(pc->pc_ucred, &p->p_acflag); + error = suser(kauth_cred_get(), &p->p_acflag); if (error) return (error); - error = copyin((void *)uap->auditinfo, (void *)p->p_au, - sizeof(*p->p_au)); + error = copyin(uap->auditinfo, + (void *)&temp_auditinfo, + sizeof(temp_auditinfo)); if (error) return (error); +#if CONFIG_MACF + error = mac_proc_check_setaudit(p, &temp_auditinfo); + if (error) + return (error); + +#endif + + + /* + * we are modifying the audit info in a credential so we need a new + * credential (or take another reference on an existing credential that + * matches our new one). We must do this because the audit info in the + * credential is used as part of our hash key. Get current credential + * in the target process and take a reference while we muck with it. + */ + for (;;) { + kauth_cred_t my_cred, my_new_cred; + + my_cred = kauth_cred_proc_ref(p); + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_setauditinfo(my_cred, &temp_auditinfo); + + if (my_cred != my_new_cred) { + proc_lock(p); + /* need to protect for a race where another thread also changed + * the credential after we took our reference. If p_ucred has + * changed then we should restart this again with the new cred. + */ + if (p->p_ucred != my_cred) { + proc_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + continue; + } + p->p_ucred = my_new_cred; + proc_unlock(p); + } + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + break; + } + + /* propagate the change from the process to Mach task */ + set_security_token(p); + + safecred = kauth_cred_proc_ref(p); + audit_arg_auditinfo(&safecred->cr_au); + kauth_cred_unref(&safecred); return (0); } -struct getaudit_addr_args { - struct auditinfo_addr *auditinfo_addr; - int length; -}; /* ARGSUSED */ int -getaudit_addr(struct proc *p, struct getaudit_addr_args *uap, register_t *retval) +getaudit_addr(__unused proc_t p, __unused struct getaudit_addr_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; - int error; - - error = suser(pc->pc_ucred, &p->p_acflag); - if (error) - return (error); return (ENOSYS); } -struct setaudit_addr_args { - struct auditinfo_addr *auditinfo_addr; - int length; -}; /* ARGSUSED */ int -setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval) +setaudit_addr(proc_t p, __unused struct setaudit_addr_args *uap, __unused register_t *retval) { - register struct pcred *pc = p->p_cred; int error; - error = suser(pc->pc_ucred, &p->p_acflag); + error = suser(kauth_cred_get(), &p->p_acflag); if (error) return (error); return (ENOSYS); @@ -731,22 +1379,17 @@ setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval /* * Syscall to manage audit files. * - * XXX: Should generate an audit event. */ -struct auditctl_args { - char *path; -}; /* ARGSUSED */ int -auditctl(struct proc *p, struct auditctl_args *uap) +auditctl(proc_t p, struct auditctl_args *uap, __unused register_t *retval) { - struct kaudit_record *ar; struct nameidata nd; - struct ucred *cred; + kauth_cred_t cred; struct vnode *vp; - int error, flags, ret; + int error; - error = suser(p->p_ucred, &p->p_acflag); + error = suser(kauth_cred_get(), &p->p_acflag); if (error) return (error); @@ -758,25 +1401,60 @@ auditctl(struct proc *p, struct auditctl_args *uap) * validity checks, and grab another reference to the current * credential. */ - if (uap->path != NULL) { - NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, - uap->path, p); - flags = audit_open_flags; - error = vn_open(&nd, flags, 0); + if (uap->path != USER_ADDR_NULL) { + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNPATH1, + (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32), + uap->path, vfs_context_current()); + error = vn_open(&nd, audit_open_flags, 0); if (error) goto out; - VOP_UNLOCK(nd.ni_vp, 0, p); vp = nd.ni_vp; + if (vp->v_type != VREG) { - vn_close(vp, audit_close_flags, p->p_ucred, p); + vn_close(vp, audit_close_flags, vfs_context_current()); + vnode_put(vp); error = EINVAL; goto out; } - cred = p->p_ucred; - crhold(cred); - } +#if CONFIG_MACF + /* + * Accessibility of the vnode was determined in + * vn_open; the mac_system_check_auditctl should only + * determine whether that vnode is appropriate for + * storing audit data, or that the caller was + * permitted to control the auditing system at all. + * For example, a confidentiality policy may want to + * ensure that audit files are always high + * sensitivity. + */ + error = mac_system_check_auditctl(kauth_cred_get(), vp); + if (error) { + vn_close(vp, audit_close_flags, vfs_context_current()); + vnode_put(vp); + goto out; + } +#endif + cred = kauth_cred_get_with_ref(); + lck_mtx_lock(audit_mtx); + audit_suspended = 0; + lck_mtx_unlock(audit_mtx); + } +#if CONFIG_MACF + else { + error = mac_system_check_auditctl(kauth_cred_get(), NULL); + if (error) + return (error); + } +#endif + + /* + * a vp and cred of NULL is valid at this point + * and indicates we're to turn off auditing... + */ audit_rotate_vnode(cred, vp); + if (vp) + vnode_put(vp); out: return (error); } @@ -789,10 +1467,11 @@ out: * MPSAFE */ struct kaudit_record * -audit_new(int event, struct proc *p, struct uthread *uthread) +audit_new(int event, proc_t p, __unused struct uthread *uthread) { struct kaudit_record *ar; int no_record; + kauth_cred_t safecred; /* * Eventually, there may be certain classes of events that @@ -805,32 +1484,24 @@ audit_new(int event, struct proc *p, struct uthread *uthread) #if 0 if (event != AUDIT_EVENT_FILESTOP && event != AUDIT_EVENT_FILESTART) { #endif - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); no_record = (audit_suspended || !audit_enabled); - mutex_unlock(audit_mtx); + lck_mtx_unlock(audit_mtx); if (no_record) return (NULL); #if 0 } #endif - /* - * Eventually, we might want to have global event filtering - * by event type here. - */ - - /* - * XXX: Process-based event preselection should occur here. - * Currently, we only post-select. - */ - /* * Initialize the audit record header. - * XXX: Should probably use a zone; whatever we use must be - * safe to call from the non-BSD side of the house. * XXX: We may want to fail-stop if allocation fails. + * XXX: The number of outstanding uncommitted audit records is + * limited by the number of concurrent threads servicing system + * calls in the kernel. */ - (void)kmem_alloc(kernel_map, &ar, sizeof(*ar)); + + ar = (struct kaudit_record *)zalloc(audit_zone); if (ar == NULL) return NULL; @@ -839,16 +1510,59 @@ audit_new(int event, struct proc *p, struct uthread *uthread) ar->k_ar.ar_event = event; nanotime(&ar->k_ar.ar_starttime); + safecred = kauth_cred_proc_ref(p); /* Export the subject credential. */ - cru2x(p->p_ucred, &ar->k_ar.ar_subj_cred); - ar->k_ar.ar_subj_ruid = p->p_cred->p_ruid; - ar->k_ar.ar_subj_rgid = p->p_cred->p_rgid; - ar->k_ar.ar_subj_egid = p->p_ucred->cr_groups[0]; - ar->k_ar.ar_subj_auid = p->p_au->ai_auid; + cru2x(safecred, &ar->k_ar.ar_subj_cred); + + ar->k_ar.ar_subj_ruid = safecred->cr_ruid; + ar->k_ar.ar_subj_rgid = safecred->cr_rgid; + ar->k_ar.ar_subj_egid = safecred->cr_groups[0]; + ar->k_ar.ar_subj_auid = safecred->cr_au.ai_auid; + ar->k_ar.ar_subj_asid = safecred->cr_au.ai_asid; + ar->k_ar.ar_subj_amask = safecred->cr_au.ai_mask; + ar->k_ar.ar_subj_term = safecred->cr_au.ai_termid; + kauth_cred_unref(&safecred); + ar->k_ar.ar_subj_pid = p->p_pid; bcopy(p->p_comm, ar->k_ar.ar_subj_comm, MAXCOMLEN); - bcopy(&p->p_au->ai_mask, &ar->k_ar.ar_subj_amask, - sizeof(p->p_au->ai_mask)); + +#if CONFIG_MACF + do { + struct mac mac; + + /* Retrieve the MAC labels for the process. */ + ar->k_ar.ar_cred_mac_labels = + (char *)zalloc(audit_mac_label_zone); + if (ar->k_ar.ar_cred_mac_labels == NULL) { + zfree(audit_zone, ar); + return (NULL); + } + mac.m_buflen = MAC_AUDIT_LABEL_LEN; + mac.m_string = ar->k_ar.ar_cred_mac_labels; + mac_cred_label_externalize_audit(p, &mac); + + /* + * grab space for the reconds. + */ + ar->k_ar.ar_mac_records = (struct mac_audit_record_list_t *) + kalloc(sizeof(*ar->k_ar.ar_mac_records)); + if (ar->k_ar.ar_mac_records == NULL) { + zfree(audit_mac_label_zone, + ar->k_ar.ar_cred_mac_labels); + zfree(audit_zone, ar); + return (NULL); + } + + LIST_INIT(ar->k_ar.ar_mac_records); + + ar->k_ar.ar_forced_by_mac = 0; + + } while (0); +#endif + + lck_mtx_lock(audit_mtx); + audit_pre_q_len++; + lck_mtx_unlock(audit_mtx); return (ar); } @@ -860,7 +1574,9 @@ audit_new(int event, struct proc *p, struct uthread *uthread) void audit_abort(struct kaudit_record *ar) { - + lck_mtx_lock(audit_mtx); + audit_pre_q_len--; + lck_mtx_unlock(audit_mtx); audit_free(ar); } @@ -870,10 +1586,59 @@ audit_abort(struct kaudit_record *ar) void audit_commit(struct kaudit_record *ar, int error, int retval) { + int ret; + int sorf; + struct au_mask *aumask; if (ar == NULL) return; + /* + * Decide whether to commit the audit record by checking the + * error value from the system call and using the appropriate + * audit mask. + */ + if (ar->k_ar.ar_subj_auid == AU_DEFAUDITID) + aumask = &audit_nae_mask; + else + aumask = &ar->k_ar.ar_subj_amask; + + if (error) + sorf = AU_PRS_FAILURE; + else + sorf = AU_PRS_SUCCESS; + + switch(ar->k_ar.ar_event) { + + case AUE_OPEN_RWTC: + /* The open syscall always writes a OPEN_RWTC event; limit the + * to the proper type of event based on the flags and the error + * value. + */ + ar->k_ar.ar_event = flags_and_error_to_openevent(ar->k_ar.ar_arg_fflags, error); + break; + + case AUE_SYSCTL: + ar->k_ar.ar_event = ctlname_to_sysctlevent(ar->k_ar.ar_arg_ctlname, ar->k_ar.ar_valid_arg); + break; + + case AUE_AUDITON: + /* Convert the auditon() command to an event */ + ar->k_ar.ar_event = auditon_command_event(ar->k_ar.ar_arg_cmd); + break; + } + + if (au_preselect(ar->k_ar.ar_event, aumask, sorf) != 0) + ar->k_ar_commit |= AR_COMMIT_KERNEL; + + if ((ar->k_ar_commit & (AR_COMMIT_USER | AR_COMMIT_KERNEL)) == 0) { + lck_mtx_lock(audit_mtx); + audit_pre_q_len--; + lck_mtx_unlock(audit_mtx); + audit_free(ar); + return; + } + ar->k_ar.ar_errno = error; ar->k_ar.ar_retval = retval; @@ -887,28 +1652,63 @@ audit_commit(struct kaudit_record *ar, int error, int retval) */ nanotime(&ar->k_ar.ar_endtime); - /* - * XXXAUDIT: The number of outstanding uncommitted audit records is - * limited by the number of concurrent threads servicing system - * calls in the kernel. However, there is currently no bound on - * the size of the committed records in the audit event queue - * before they are sent to disk. Probably, there should be a fixed - * size bound (perhaps configurable), and if that bound is reached, - * threads should sleep in audit_commit() until there's room. - */ - mutex_lock(audit_mtx); + lck_mtx_lock(audit_mtx); /* * Note: it could be that some records initiated while audit was * enabled should still be committed? */ if (audit_suspended || !audit_enabled) { - mutex_unlock(audit_mtx); + audit_pre_q_len--; + lck_mtx_unlock(audit_mtx); audit_free(ar); return; } + + /* + * Constrain the number of committed audit records based on + * the configurable parameter. + */ + while (audit_q_len >= audit_qctrl.aq_hiwater) { + + ret = wait_queue_assert_wait(audit_wait_queue, + AUDIT_COMMIT_EVENT, + THREAD_UNINT, + 0); + lck_mtx_unlock(audit_mtx); + + assert(ret == THREAD_WAITING); + + ret = thread_block(THREAD_CONTINUE_NULL); + assert(ret == THREAD_AWAKENED); + lck_mtx_lock(audit_mtx); + } + TAILQ_INSERT_TAIL(&audit_q, ar, k_q); - wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED); - mutex_unlock(audit_mtx); + audit_q_len++; + audit_pre_q_len--; + wait_queue_wakeup_one(audit_wait_queue, AUDIT_WORKER_EVENT, THREAD_AWAKENED); + lck_mtx_unlock(audit_mtx); +} + +/* + * If we're out of space and need to suspend unprivileged + * processes, do that here rather than trying to allocate + * another audit record. + */ +static void +audit_new_wait(int audit_event, proc_t proc, struct uthread *uthread) +{ + int ret; + + if (audit_in_failure && + suser(kauth_cred_get(), &proc->p_acflag) != 0) { + ret = wait_queue_assert_wait(audit_wait_queue, + AUDIT_FAILURE_EVENT, THREAD_UNINT, 0); + assert(ret == THREAD_WAITING); + (void)thread_block(THREAD_CONTINUE_NULL); + panic("audit_failing_stop: thread continued"); + } + uthread->uu_ar = audit_new(audit_event, proc, uthread); } /* @@ -916,35 +1716,76 @@ audit_commit(struct kaudit_record *ar, int error, int retval) * each system call. */ void -audit_syscall_enter(unsigned short code, struct proc *proc, +audit_syscall_enter(unsigned short code, proc_t proc, struct uthread *uthread) { int audit_event; + struct au_mask *aumask; + kauth_cred_t my_cred; + + audit_event = sys_au_event[code]; + if (audit_event == AUE_NULL) + return; assert(uthread->uu_ar == NULL); - audit_event = sys_au_event[code]; + /* Check which audit mask to use; either the kernel non-attributable + * event mask or the process audit mask. + */ + my_cred = kauth_cred_proc_ref(proc); + + if (my_cred->cr_au.ai_auid == AU_DEFAUDITID) + aumask = &audit_nae_mask; + else + aumask = &my_cred->cr_au.ai_mask; /* - * Allocate an audit record, if desired, and store in the BSD - * thread for later use. + * Allocate an audit record, if preselection allows it, and store + * in the BSD thread for later use. */ - if (audit_event != AUE_NULL) { -#if 0 - AUDIT_PRINTF(("Allocated record type %d for syscall %d\n", - audit_event, code)); -#endif - if (au_preselect(audit_event, &proc->p_au->ai_mask, - AU_PRS_FAILURE | AU_PRS_SUCCESS)) { + +#if CONFIG_MACF + do { + int error; + + error = mac_audit_check_preselect(my_cred, code, + (void *) uthread->uu_arg); + + if (error == MAC_AUDIT_YES) { uthread->uu_ar = audit_new(audit_event, proc, uthread); - } else { + uthread->uu_ar->k_ar.ar_forced_by_mac = 1; + au_to_text("Forced by a MAC policy"); + } + else if (error == MAC_AUDIT_NO) { uthread->uu_ar = NULL; } + else if (error == MAC_AUDIT_DEFAULT && + au_preselect(audit_event, &my_cred->cr_au.ai_mask, + AU_PRS_FAILURE | AU_PRS_SUCCESS)) + audit_new_wait(audit_event, proc, uthread); + } while (0); +#else + if (au_preselect(audit_event, &my_cred->cr_au.ai_mask, + AU_PRS_FAILURE | AU_PRS_SUCCESS)) { + audit_new_wait(audit_event, proc, uthread); + } else { + uthread->uu_ar = NULL; } +#endif + kauth_cred_unref(&my_cred); } +/* + * Note: The audit_syscall_exit() parameter list was modified to support + * mac_audit_check_postselect(), which requires the Darwin syscall number. + */ +#if CONFIG_MACF +void +audit_syscall_exit(unsigned short code, int error, AUDIT_PRINTF_ONLY proc_t proc, struct uthread *uthread) +#else void -audit_syscall_exit(int error, struct proc *proc, struct uthread *uthread) +audit_syscall_exit(int error, AUDIT_PRINTF_ONLY proc_t proc, struct uthread *uthread) +#endif { int retval; @@ -961,9 +1802,104 @@ audit_syscall_exit(int error, struct proc *proc, struct uthread *uthread) else retval = uthread->uu_rval[0]; +#if CONFIG_MACF + do { + int mac_error; + + if (uthread->uu_ar == NULL) /* syscall wasn't audited */ + goto out; + + /* + * Note, no other postselect mechanism exists. If + * mac_audit_check_postselect returns MAC_AUDIT_NO, the + * record will be suppressed. Other values at this + * point result in the audit record being committed. + * This suppression behavior will probably go away in + * the port to 10.3.4. + */ + mac_error = mac_audit_check_postselect(kauth_cred_get(), code, + (void *) uthread->uu_arg, error, retval, + uthread->uu_ar->k_ar.ar_forced_by_mac); + + if (mac_error == MAC_AUDIT_YES) + uthread->uu_ar->k_ar_commit |= AR_COMMIT_KERNEL; + else if (mac_error == MAC_AUDIT_NO) { + audit_free(uthread->uu_ar); + goto out; + } + + } while (0); + +#endif audit_commit(uthread->uu_ar, error, retval); - if (uthread->uu_ar != NULL) + if (uthread->uu_ar != NULL) { AUDIT_PRINTF(("audit record committed by pid %d\n", proc->p_pid)); + } + +#if CONFIG_MACF +out: +#endif + uthread->uu_ar = NULL; + +} + +/* + * Calls to set up and tear down audit structures used during Mach + * system calls. + */ +void +audit_mach_syscall_enter(unsigned short audit_event) +{ + struct uthread *uthread; + proc_t proc; + struct au_mask *aumask; + kauth_cred_t my_cred; + + if (audit_event == AUE_NULL) + return; + + uthread = curuthread(); + if (uthread == NULL) + return; + + proc = current_proc(); + if (proc == NULL) + return; + + assert(uthread->uu_ar == NULL); + + my_cred = kauth_cred_proc_ref(proc); + + /* Check which audit mask to use; either the kernel non-attributable + * event mask or the process audit mask. + */ + if (my_cred->cr_au.ai_auid == AU_DEFAUDITID) + aumask = &audit_nae_mask; + else + aumask = &my_cred->cr_au.ai_mask; + + kauth_cred_unref(&my_cred); + + /* + * Allocate an audit record, if desired, and store in the BSD + * thread for later use. + */ + if (au_preselect(audit_event, aumask, + AU_PRS_FAILURE | AU_PRS_SUCCESS)) { + uthread->uu_ar = audit_new(audit_event, proc, uthread); + } else { + uthread->uu_ar = NULL; + } +} + +void +audit_mach_syscall_exit(int retval, struct uthread *uthread) +{ + /* The error code from Mach system calls is the same as the + * return value + */ + /* XXX Is the above statement always true? */ + audit_commit(uthread->uu_ar, retval, retval); uthread->uu_ar = NULL; } @@ -977,7 +1913,7 @@ audit_syscall_exit(int error, struct proc *proc, struct uthread *uthread) * record for this event. */ void -audit_arg_accmode(int accmode) +audit_arg_addr(user_addr_t addr) { struct kaudit_record *ar; @@ -985,12 +1921,12 @@ audit_arg_accmode(int accmode) if (ar == NULL) return; - ar->k_ar.ar_arg_accmode = accmode; - ar->k_ar.ar_valid_arg |= ARG_ACCMODE; + ar->k_ar.ar_arg_addr = CAST_DOWN(void *, addr); /* XXX */ + ar->k_ar.ar_valid_arg |= ARG_ADDR; } void -audit_arg_cmode(int cmode) +audit_arg_len(user_size_t len) { struct kaudit_record *ar; @@ -998,8 +1934,8 @@ audit_arg_cmode(int cmode) if (ar == NULL) return; - ar->k_ar.ar_arg_cmode = cmode; - ar->k_ar.ar_valid_arg |= ARG_CMODE; + ar->k_ar.ar_arg_len = CAST_DOWN(int, len); /* XXX */ + ar->k_ar.ar_valid_arg |= ARG_LEN; } void @@ -1061,9 +1997,9 @@ audit_arg_uid(uid_t uid, uid_t euid, uid_t ruid, uid_t suid) } void -audit_arg_groupset(gid_t *gidset, u_int gidset_size) +audit_arg_groupset(const gid_t *gidset, u_int gidset_size) { - int i; + uint i; struct kaudit_record *ar; ar = currecord(); @@ -1077,7 +2013,7 @@ audit_arg_groupset(gid_t *gidset, u_int gidset_size) } void -audit_arg_login(char *login) +audit_arg_login(const char *login) { struct kaudit_record *ar; @@ -1085,18 +2021,25 @@ audit_arg_login(char *login) if (ar == NULL) return; -#if 0 - /* - * XXX: Add strlcpy() to Darwin for improved safety. - */ strlcpy(ar->k_ar.ar_arg_login, login, MAXLOGNAME); -#else - strcpy(ar->k_ar.ar_arg_login, login); -#endif ar->k_ar.ar_valid_arg |= ARG_LOGIN; } +void +audit_arg_ctlname(const int *name, int namelen) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + bcopy(name, &ar->k_ar.ar_arg_ctlname, namelen * sizeof(int)); + ar->k_ar.ar_arg_len = namelen; + ar->k_ar.ar_valid_arg |= (ARG_CTLNAME | ARG_LEN); +} + void audit_arg_mask(int mask) { @@ -1136,6 +2079,19 @@ audit_arg_dev(int dev) ar->k_ar.ar_valid_arg |= ARG_DEV; } +void +audit_arg_value(long value) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_value = value; + ar->k_ar.ar_valid_arg |= ARG_VALUE; +} + void audit_arg_owner(uid_t uid, gid_t gid) { @@ -1163,6 +2119,30 @@ audit_arg_pid(pid_t pid) ar->k_ar.ar_valid_arg |= ARG_PID; } +void +audit_arg_process(proc_t p) +{ + struct kaudit_record *ar; + kauth_cred_t my_cred; + + ar = currecord(); + if ((ar == NULL) || (p == NULL)) + return; + + my_cred = kauth_cred_proc_ref(p); + ar->k_ar.ar_arg_auid = my_cred->cr_au.ai_auid; + ar->k_ar.ar_arg_euid = my_cred->cr_uid; + ar->k_ar.ar_arg_egid = my_cred->cr_groups[0]; + ar->k_ar.ar_arg_ruid = my_cred->cr_ruid; + ar->k_ar.ar_arg_rgid = my_cred->cr_rgid; + ar->k_ar.ar_arg_asid = my_cred->cr_au.ai_asid; + ar->k_ar.ar_arg_termid = my_cred->cr_au.ai_termid; + kauth_cred_unref(&my_cred); + + ar->k_ar.ar_valid_arg |= ARG_AUID | ARG_EUID | ARG_EGID | ARG_RUID | + ARG_RGID | ARG_ASID | ARG_TERMID | ARG_PROCESS; +} + void audit_arg_signum(u_int signum) { @@ -1186,19 +2166,26 @@ audit_arg_socket(int sodomain, int sotype, int soprotocol) if (ar == NULL) return; - ar->k_ar.ar_arg_sockinfo.sodomain = sodomain; - ar->k_ar.ar_arg_sockinfo.sotype = sotype; - ar->k_ar.ar_arg_sockinfo.soprotocol = soprotocol; + ar->k_ar.ar_arg_sockinfo.so_domain = sodomain; + ar->k_ar.ar_arg_sockinfo.so_type = sotype; + ar->k_ar.ar_arg_sockinfo.so_protocol = soprotocol; ar->k_ar.ar_valid_arg |= ARG_SOCKINFO; } +/* + * Note that the current working directory vp must be supplied at the audit + * call site to permit per thread current working directories, and that it + * must take a upath starting with '/' into account for chroot if the path + * is absolute. This results in the real (non-chroot) path being recorded + * in the audit record. + */ void -audit_arg_sockaddr(struct proc *p, struct sockaddr *so) +audit_arg_sockaddr(struct vnode *cwd_vp, struct sockaddr *so) { struct kaudit_record *ar; ar = currecord(); - if (ar == NULL || p == NULL || so == NULL) + if (ar == NULL || cwd_vp == NULL || so == NULL) return; bcopy(so, &ar->k_ar.ar_arg_sockaddr, sizeof(ar->k_ar.ar_arg_sockaddr)); @@ -1210,7 +2197,7 @@ audit_arg_sockaddr(struct proc *p, struct sockaddr *so) ar->k_ar.ar_valid_arg |= ARG_SADDRINET6; break; case AF_UNIX: - audit_arg_upath(p, ((struct sockaddr_un *)so)->sun_path, + audit_arg_upath(cwd_vp, ((struct sockaddr_un *)so)->sun_path, ARG_UPATH1); ar->k_ar.ar_valid_arg |= ARG_SADDRUNIX; break; @@ -1231,7 +2218,25 @@ audit_arg_auid(uid_t auid) } void -audit_arg_text(char *text) +audit_arg_auditinfo(const struct auditinfo *au_info) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_auid = au_info->ai_auid; + ar->k_ar.ar_arg_asid = au_info->ai_asid; + ar->k_ar.ar_arg_amask.am_success = au_info->ai_mask.am_success; + ar->k_ar.ar_arg_amask.am_failure = au_info->ai_mask.am_failure; + ar->k_ar.ar_arg_termid.port = au_info->ai_termid.port; + ar->k_ar.ar_arg_termid.machine = au_info->ai_termid.machine; + ar->k_ar.ar_valid_arg |= ARG_AUID | ARG_ASID | ARG_AMASK | ARG_TERMID; +} + +void +audit_arg_text(const char *text) { struct kaudit_record *ar; @@ -1245,12 +2250,12 @@ audit_arg_text(char *text) return; if (ar->k_ar.ar_arg_text == NULL) { - kmem_alloc(kernel_map, &ar->k_ar.ar_arg_text, MAXPATHLEN); + ar->k_ar.ar_arg_text = (char *)kalloc(MAXPATHLEN); if (ar->k_ar.ar_arg_text == NULL) return; } - strcpy(ar->k_ar.ar_arg_text, text); + strlcpy(ar->k_ar.ar_arg_text, text, MAXPATHLEN); ar->k_ar.ar_valid_arg |= ARG_TEXT; } @@ -1281,7 +2286,7 @@ audit_arg_svipc_cmd(int cmd) } void -audit_arg_svipc_perm(struct ipc_perm *perm) +audit_arg_svipc_perm(const struct ipc_perm *perm) { struct kaudit_record *ar; @@ -1308,7 +2313,7 @@ audit_arg_svipc_id(int id) } void -audit_arg_svipc_addr(void * addr) +audit_arg_svipc_addr(user_addr_t addr) { struct kaudit_record *ar; @@ -1320,66 +2325,101 @@ audit_arg_svipc_addr(void * addr) ar->k_ar.ar_valid_arg |= ARG_SVIPC_ADDR; } -/* - * Initialize the audit information for the a process, presumably the first - * process in the system. - * XXX It is not clear what the initial values should be for audit ID, - * session ID, etc. - */ void -audit_proc_init(struct proc *p) +audit_arg_posix_ipc_perm(uid_t uid, gid_t gid, mode_t mode) { - MALLOC_ZONE(p->p_au, struct auditinfo *, sizeof(*p->p_au), - M_SUBPROC, M_WAITOK); + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; - bzero((void *)p->p_au, sizeof(*p->p_au)); + ar->k_ar.ar_arg_pipc_perm.pipc_uid = uid; + ar->k_ar.ar_arg_pipc_perm.pipc_gid = gid; + ar->k_ar.ar_arg_pipc_perm.pipc_mode = mode; + ar->k_ar.ar_valid_arg |= ARG_POSIX_IPC_PERM; } -/* - * Copy the audit info from the parent process to the child process when - * a fork takes place. - * XXX Need to check for failure from the memory allocation, in here - * as well as in any functions that use the process auditing info. - */ void -audit_proc_fork(struct proc *parent, struct proc *child) +audit_arg_auditon(const union auditon_udata *udata) { - /* Always set up the audit information pointer as this function - * should only be called when the proc is new. If proc structures - * are ever cached and reused, then this behavior will leak memory. - */ - MALLOC_ZONE(child->p_au, struct auditinfo *, sizeof(*child->p_au), - M_SUBPROC, M_WAITOK); + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; - bcopy(parent->p_au, child->p_au, sizeof(*child->p_au)); + bcopy((const void *)udata, &ar->k_ar.ar_arg_auditon, + sizeof(ar->k_ar.ar_arg_auditon)); + ar->k_ar.ar_valid_arg |= ARG_AUDITON; } -/* - * Free the auditing structure for the process. +/* + * Audit information about a file, either the file's vnode info, or its + * socket address info. */ void -audit_proc_free(struct proc *p) +audit_arg_file(__unused proc_t p, const struct fileproc *fp) { - FREE_ZONE((void *)p->p_au, sizeof(*p->p_au), M_SUBPROC); - p->p_au = NULL; + struct kaudit_record *ar; + struct socket *so; + struct inpcb *pcb; + + if (fp->f_fglob->fg_type == DTYPE_VNODE) { + audit_arg_vnpath_withref((struct vnode *)fp->f_fglob->fg_data, ARG_VNODE1); + return; + } + + if (fp->f_fglob->fg_type == DTYPE_SOCKET) { + ar = currecord(); + if (ar == NULL) + return; + so = (struct socket *)fp->f_fglob->fg_data; + if (INP_CHECK_SOCKAF(so, PF_INET)) { + if (so->so_pcb == NULL) + return; + ar->k_ar.ar_arg_sockinfo.so_type = + so->so_type; + ar->k_ar.ar_arg_sockinfo.so_domain = + INP_SOCKAF(so); + ar->k_ar.ar_arg_sockinfo.so_protocol = + so->so_proto->pr_protocol; + pcb = (struct inpcb *)so->so_pcb; + ar->k_ar.ar_arg_sockinfo.so_raddr = + pcb->inp_faddr.s_addr; + ar->k_ar.ar_arg_sockinfo.so_laddr = + pcb->inp_laddr.s_addr; + ar->k_ar.ar_arg_sockinfo.so_rport = + pcb->inp_fport; + ar->k_ar.ar_arg_sockinfo.so_lport = + pcb->inp_lport; + ar->k_ar.ar_valid_arg |= ARG_SOCKINFO; + } + } + } + /* * Store a path as given by the user process for auditing into the audit * record stored on the user thread. This function will allocate the memory to * store the path info if not already available. This memory will be - * freed when the audit record is freed. + * freed when the audit record is freed. Note that the current working + * directory vp must be supplied at the audit call site to permit per thread + * current working directories, and that it must take a upath starting with + * '/' into account for chroot if the path is absolute. This results in the + * real (non-chroot) path being recorded in the audit record. */ void -audit_arg_upath(struct proc *p, char *upath, u_int64_t flags) +audit_arg_upath(struct vnode *cwd_vp, char *upath, u_int64_t flags) { struct kaudit_record *ar; char **pathp; - if (p == NULL || upath == NULL) + if (cwd_vp == NULL || upath == NULL) return; /* nothing to do! */ - if (flags & (ARG_UPATH1 | ARG_UPATH2) == 0) + if ((flags & (ARG_UPATH1 | ARG_UPATH2)) == 0) return; ar = currecord(); @@ -1396,17 +2436,20 @@ audit_arg_upath(struct proc *p, char *upath, u_int64_t flags) } if (*pathp == NULL) { - kmem_alloc(kernel_map, pathp, MAXPATHLEN); + *pathp = (char *)kalloc(MAXPATHLEN); if (*pathp == NULL) return; } - canon_path(p, upath, *pathp); - - if (flags & ARG_UPATH1) - ar->k_ar.ar_valid_arg |= ARG_UPATH1; - else - ar->k_ar.ar_valid_arg |= ARG_UPATH2; + if (canon_path(cwd_vp, upath, *pathp) == 0) { + if (flags & ARG_UPATH1) + ar->k_ar.ar_valid_arg |= ARG_UPATH1; + else + ar->k_ar.ar_valid_arg |= ARG_UPATH2; + } else { + kfree(*pathp, MAXPATHLEN); + *pathp = NULL; + } } /* @@ -1414,7 +2457,7 @@ audit_arg_upath(struct proc *p, char *upath, u_int64_t flags) * record. * * It is assumed that the caller will hold any vnode locks necessary to - * perform a VOP_GETATTR() on the passed vnode. + * perform a VNOP_GETATTR() on the passed vnode. * * XXX: The attr code is very similar to vfs_vnops.c:vn_stat(), but * always provides access to the generation number as we need that @@ -1427,12 +2470,16 @@ void audit_arg_vnpath(struct vnode *vp, u_int64_t flags) { struct kaudit_record *ar; - struct vattr vattr; + struct vnode_attr va; int error; int len; char **pathp; struct vnode_au_info *vnp; - struct proc *p; + proc_t p; +#if CONFIG_MACF + char **vnode_mac_labelp; + struct mac mac; +#endif if (vp == NULL) return; @@ -1441,7 +2488,7 @@ audit_arg_vnpath(struct vnode *vp, u_int64_t flags) if (ar == NULL) /* This will be the case for unaudited system calls */ return; - if (flags & (ARG_VNODE1 | ARG_VNODE2) == 0) + if ((flags & (ARG_VNODE1 | ARG_VNODE2)) == 0) return; p = current_proc(); @@ -1451,45 +2498,78 @@ audit_arg_vnpath(struct vnode *vp, u_int64_t flags) ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE1); pathp = &ar->k_ar.ar_arg_kpath1; vnp = &ar->k_ar.ar_arg_vnode1; +#if CONFIG_MACF + vnode_mac_labelp = &ar->k_ar.ar_vnode1_mac_labels; +#endif } else { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_KPATH2); ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE2); pathp = &ar->k_ar.ar_arg_kpath2; vnp = &ar->k_ar.ar_arg_vnode2; +#if CONFIG_MACF + vnode_mac_labelp = &ar->k_ar.ar_vnode2_mac_labels; +#endif } if (*pathp == NULL) { - kmem_alloc(kernel_map, pathp, MAXPATHLEN); + *pathp = (char *)kalloc(MAXPATHLEN); if (*pathp == NULL) return; } - /* Copy the path looked up by the vn_getpath() function */ + /* + * If vn_getpath() succeeds, place it in a string buffer + * attached to the audit record, and set a flag indicating + * it is present. + */ len = MAXPATHLEN; - vn_getpath(vp, *pathp, &len); + if (vn_getpath(vp, *pathp, &len) == 0) { if (flags & ARG_VNODE1) ar->k_ar.ar_valid_arg |= ARG_KPATH1; else ar->k_ar.ar_valid_arg |= ARG_KPATH2; + } else { + kfree(*pathp, MAXPATHLEN); + *pathp = NULL; + } - /* - * XXX: We'd assert the vnode lock here, only Darwin doesn't - * appear to have vnode locking assertions. - */ - error = VOP_GETATTR(vp, &vattr, p->p_ucred, p); + VATTR_INIT(&va); + VATTR_WANTED(&va, va_mode); + VATTR_WANTED(&va, va_uid); + VATTR_WANTED(&va, va_gid); + VATTR_WANTED(&va, va_rdev); + VATTR_WANTED(&va, va_fsid); + VATTR_WANTED(&va, va_fileid); + VATTR_WANTED(&va, va_gen); + error = vnode_getattr(vp, &va, vfs_context_current()); if (error) { /* XXX: How to handle this case? */ return; } - vnp->vn_mode = vattr.va_mode; - vnp->vn_uid = vattr.va_uid; - vnp->vn_gid = vattr.va_gid; - vnp->vn_dev = vattr.va_rdev; - vnp->vn_fsid = vattr.va_fsid; - vnp->vn_fileid = vattr.va_fileid; - vnp->vn_gen = vattr.va_gen; +#if CONFIG_MACF + if (*vnode_mac_labelp == NULL) { + *vnode_mac_labelp = (char *)zalloc(audit_mac_label_zone); + if (*vnode_mac_labelp != NULL) { + mac.m_buflen = MAC_AUDIT_LABEL_LEN; + mac.m_string = *vnode_mac_labelp; + mac_vnode_label_externalize_audit(vp, &mac); + } + + + + } +#endif + + /* XXX do we want to fall back here when these aren't supported? */ + vnp->vn_mode = va.va_mode; + vnp->vn_uid = va.va_uid; + vnp->vn_gid = va.va_gid; + vnp->vn_dev = va.va_rdev; + vnp->vn_fsid = va.va_fsid; + vnp->vn_fileid = (u_long)va.va_fileid; + vnp->vn_gen = va.va_gen; if (flags & ARG_VNODE1) ar->k_ar.ar_valid_arg |= ARG_VNODE1; else @@ -1497,96 +2577,222 @@ audit_arg_vnpath(struct vnode *vp, u_int64_t flags) } -#else /* !AUDIT */ +void +audit_arg_vnpath_withref(struct vnode *vp, u_int64_t flags) +{ + if (vp == NULL || vnode_getwithref(vp)) + return; + audit_arg_vnpath(vp, flags); + (void)vnode_put(vp); +} void -audit_init(void) +audit_arg_mach_port1(mach_port_name_t port) { + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + ar->k_ar.ar_arg_mach_port1 = port; + ar->k_ar.ar_valid_arg |= ARG_MACHPORT1; } void -audit_shutdown(void) +audit_arg_mach_port2(mach_port_name_t port) { + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + ar->k_ar.ar_arg_mach_port2 = port; + ar->k_ar.ar_valid_arg |= ARG_MACHPORT2; } +/* + * The close() system call uses it's own audit call to capture the + * path/vnode information because those pieces are not easily obtained + * within the system call itself. + */ +void +audit_sysclose(proc_t p, int fd) +{ + struct fileproc *fp; + struct vnode *vp; + + audit_arg_fd(fd); + + if (fp_getfvp(p, fd, &fp, &vp) != 0) + return; + + audit_arg_vnpath_withref((struct vnode *)fp->f_fglob->fg_data, ARG_VNODE1); + file_drop(fd); +} + +#if CONFIG_MACF +/* + * This function is called by the MAC Framework to add audit data + * from a policy to the current audit record. + */ int -audit(struct proc *p, struct audit_args *uap, register_t *retval) +audit_mac_data(int type, int len, u_char *data) { + struct kaudit_record *cur; + struct mac_audit_record *record; + int ret = 0; + + if (audit_enabled == 0) { + ret = ENOTSUP; + goto out_fail; + } + + cur = currecord(); + if (cur == NULL) { + ret = ENOTSUP; + goto out_fail; + } + + /* + * XXX: Note that we silently drop the audit data if this + * allocation fails - this is consistent with the rest of the + * audit implementation. + */ + record = (struct mac_audit_record *)kalloc(sizeof(*record)); + if (record == NULL) + goto out_fail; + + record->type = type; + record->length = len; + record->data = data; + LIST_INSERT_HEAD(cur->k_ar.ar_mac_records, record, records); + + return (0); + +out_fail: + kfree(data, len); + return (ret); +} + +void +audit_arg_mac_string(const char *string) { - return (ENOSYS); + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + if (ar->k_ar.ar_arg_mac_string == NULL) { + ar->k_ar.ar_arg_mac_string = + (char *)kalloc(MAC_MAX_LABEL_BUF_LEN + MAC_ARG_PREFIX_LEN); + /* This should be a rare event. If kalloc() returns NULL, the + * system is low on kernel virtual memory. To be consistent with the + * rest of audit, just return (may need to panic if required to for audit6). + */ + if (ar->k_ar.ar_arg_mac_string == NULL) + return; + } + strncpy(ar->k_ar.ar_arg_mac_string, MAC_ARG_PREFIX, MAC_ARG_PREFIX_LEN); + strncpy(ar->k_ar.ar_arg_mac_string + MAC_ARG_PREFIX_LEN, string, MAC_MAX_LABEL_BUF_LEN); + ar->k_ar.ar_valid_arg |= ARG_MAC_STRING; + } +#endif /* MAC */ + +/* + * kau_will_audit can be used by a security policy to determine + * if an audit record will be stored, reducing wasted memory allocation + * and string handling. + */ int -auditon(struct proc *p, struct auditon_args *uap, register_t *retval) +kau_will_audit(void) { - return (ENOSYS); + + return (audit_enabled && currecord() != NULL); +} + +#else /* !AUDIT */ + +void +audit_init(void) +{ + +} + +void +audit_shutdown(void) +{ + } int -auditsvc(struct proc *p, struct auditsvc_args *uap, register_t *retval) +audit(proc_t p, struct audit_args *uap, register_t *retval) { return (ENOSYS); } int -getauid(struct proc *p, struct getauid_args *uap, register_t *retval) +auditon(proc_t p, struct auditon_args *uap, register_t *retval) { return (ENOSYS); } int -setauid(struct proc *p, struct setauid_args *uap, register_t *retval) +getauid(proc_t p, struct getauid_args *uap, register_t *retval) { return (ENOSYS); } int -getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval) +setauid(proc_t p, struct setauid_args *uap, register_t *retval) { return (ENOSYS); } int -setaudit(struct proc *p, struct setaudit_args *uap, register_t *retval) +getaudit(proc_t p, struct getaudit_args *uap, register_t *retval) { return (ENOSYS); } int -getaudit_addr(struct proc *p, struct getaudit_addr_args *uap, register_t *retval) +setaudit(proc_t p, struct setaudit_args *uap, register_t *retval) { return (ENOSYS); } int -setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval) +getaudit_addr(proc_t p, struct getaudit_addr_args *uap, register_t *retval) { return (ENOSYS); } int -auditctl(struct proc *p, struct auditctl_args *uap, register_t *retval) +setaudit_addr(proc_t p, struct setaudit_addr_args *uap, register_t *retval) { return (ENOSYS); } -void -audit_proc_init(struct proc *p) +int +auditctl(proc_t p, struct auditctl_args *uap, register_t *retval) { - + return (ENOSYS); } +#if CONFIG_MACF void -audit_proc_fork(struct proc *parent, struct proc *child) +audit_mac_data(int type, int len, u_char *data) { - } -void -audit_proc_free(struct proc *p) +int +kau_will_audit() { - + return (0); } +#endif #endif /* AUDIT */