/* * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * 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. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef AUDIT /* * The AUDIT_EXCESSIVELY_VERBOSE define enables a number of * gratuitously noisy printf's to the console. Due to the * volume, it should be left off unless you want your system * to churn a lot whenever the audit record flow gets high. */ /* #define AUDIT_EXCESSIVELY_VERBOSE */ #ifdef AUDIT_EXCESSIVELY_VERBOSE #define AUDIT_PRINTF(x) printf x #else #define AUDIT_PRINTF(X) #endif #if DIAGNOSTIC #if defined(assert) #undef assert() #endif #define assert(cond) \ ((void) ((cond) ? 0 : panic("%s:%d (%s)", __FILE__, __LINE__, # cond))) #else #include #endif /* DIAGNOSTIC */ /* * Define the audit control flags. */ int audit_enabled; int audit_suspended; /* * Mutex to protect global variables shared between various threads and * processes. */ static mutex_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. */ static TAILQ_HEAD(, kaudit_record) audit_q; /* * 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; /* * When an audit log is rotated, the actual rotation must be performed * by the audit worker thread, as it may have outstanding writes on the * current audit log. audit_replacement_vp holds the vnode replacing * the current vnode. We can't let more than one replacement occur * at a time, so if more than one thread requests a replacement, only * one can have the replacement "in progress" at any given moment. If * a thread tries to replace the audit vnode and discovers a replacement * is already in progress (i.e., audit_replacement_flag != 0), then it * will sleep on audit_replacement_cv waiting its turn to perform a * replacement. When a replacement is completed, this cv is signalled * 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_flag; static struct vnode *audit_replacement_vp; static struct ucred *audit_replacement_cred; /* * 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; /* * XXX: Couldn't find the include file for this, so copied kern_exec.c's * behavior. */ extern task_t kernel_task; 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); } if (ar->k_ar.ar_arg_upath2 != NULL) { kmem_free(kernel_map, 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); } if (ar->k_ar.ar_arg_kpath2 != NULL) { kmem_free(kernel_map, 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); } if (ar->k_udata != NULL) { kmem_free(kernel_map, ar->k_udata, ar->k_ulen); } kmem_free(kernel_map, ar, sizeof(*ar)); } static int audit_write(struct vnode *vp, struct kaudit_record *ar, struct ucred *cred, struct proc *p) { int ret; struct au_record *bsm; /* * If there is a user audit record attached to the kernel record, * then write the user record. */ /* XXX Need to decide a few things here: IF the user audit * record is written, but the write of the kernel record fails, * what to do? Should the kernel record come before or after the * 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); } /* * Convert the internal kernel record to BSM format and write it * out if everything's OK. */ ret = kaudit_to_bsm(ar, &bsm); if (ret == BSM_NOAUDIT) return (0); if (ret == BSM_FAILURE) { AUDIT_PRINTF(("BSM conversion failure\n")); return (-1); } /* XXX This function can be called with the kernel funnel held, * which is not optimal. 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)); kau_free(bsm); return (ret); } static void audit_worker() { int do_replacement_signal, error, release_funnel; TAILQ_HEAD(, kaudit_record) ar_worklist; struct kaudit_record *ar, *ar_start, *ar_stop; struct vnode *audit_vp, *old_vp; struct ucred *audit_cred, *old_cred; struct proc *audit_p; AUDIT_PRINTF(("audit_worker starting\n")); TAILQ_INIT(&ar_worklist); audit_cred = NULL; 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); while (1) { /* * 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 * are right. */ do_replacement_signal = 0; while (audit_replacement_flag != 0) { old_cred = audit_cred; old_vp = audit_vp; audit_cred = audit_replacement_cred; audit_vp = audit_replacement_vp; audit_replacement_cred = NULL; 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; 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; } /* * Signal that replacement have occurred to wake up and * start any other replacements started in parallel. We can * continue about our business in the mean time. We * broadcast so that both new replacements can be inserted, * but also so that the source(s) of replacement can return * successfully. */ if (do_replacement_signal) wait_queue_wakeup_all(audit_replacement_wait_queue, 0, THREAD_AWAKENED); /* * Next, check to see if we have any records to drain into * the vnode. If not, go back to waiting for an event. */ if (TAILQ_EMPTY(&audit_q)) { int ret; AUDIT_PRINTF(("audit_worker waiting\n")); ret = wait_queue_assert_wait(audit_wait_queue, 0, THREAD_UNINT); mutex_unlock(audit_mtx); assert(ret == THREAD_WAITING); ret = thread_block(THREAD_CONTINUE_NULL); assert(ret == THREAD_AWAKENED); AUDIT_PRINTF(("audit_worker woken up\n")); AUDIT_PRINTF(("audit_worker: new vp = %p; value of flag %d\n", audit_replacement_vp, audit_replacement_flag)); mutex_lock(audit_mtx); continue; } /* * If we have records, but there's no active vnode to * write to, drain the record queue. Generally, we * prevent the unnecessary allocation of records * elsewhere, but we need to allow for races between * conditional allocation and queueing. Go back to * waiting when we're done. * * XXX: We go out of our way to avoid calling audit_free() * with the audit_mtx held, to avoid a lock order reversal * as free() may grab the funnel. This will be fixed at * some point. */ if (audit_vp == NULL) { while ((ar = TAILQ_FIRST(&audit_q))) { TAILQ_REMOVE(&audit_q, ar, k_q); TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); } mutex_unlock(audit_mtx); while ((ar = TAILQ_FIRST(&ar_worklist))) { TAILQ_REMOVE(&ar_worklist, ar, k_q); audit_free(ar); } mutex_lock(audit_mtx); continue; } /* * We have both records to write, and an active vnode * to write to. Dequeue a record, and start the write. * Eventually, it might make sense to dequeue several * records and perform our own clustering, if the lower * layers aren't doing it automatically enough. * * XXX: We go out of our way to avoid calling audit_free() * with the audit_mtx held, to avoid a lock order reversal * as free() may grab the funnel. This will be fixed at * some point. */ while ((ar = TAILQ_FIRST(&audit_q))) { TAILQ_REMOVE(&audit_q, ar, k_q); TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); } mutex_unlock(audit_mtx); release_funnel = 0; while ((ar = TAILQ_FIRST(&ar_worklist))) { TAILQ_REMOVE(&ar_worklist, ar, k_q); if (audit_vp != NULL) { /* * 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) printf("audit_worker: write error %d\n", error); } audit_free(ar); } if (release_funnel) thread_funnel_set(kernel_flock, FALSE); mutex_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_enabled = 0; audit_suspended = 0; audit_replacement_cred = NULL; audit_replacement_flag = 0; audit_replacement_vp = NULL; audit_mtx = mutex_alloc(ETAP_NO_TRACE); audit_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO); audit_replacement_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO); /* Initialize the BSM audit subsystem. */ kau_init(); kernel_thread(kernel_task, audit_worker); } static void audit_rotate_vnode(struct ucred *cred, struct vnode *vp) { int ret; /* * If other parallel log replacements have been requested, we wait * until they've finished before continuing. */ mutex_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); assert(ret == THREAD_WAITING); ret = thread_block(THREAD_CONTINUE_NULL); assert(ret == THREAD_AWAKENED); AUDIT_PRINTF(("audit_rotate_vnode: woken up (flag %d)\n", audit_replacement_flag)); mutex_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. */ wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED); /* * Wait for the audit_worker to broadcast that a replacement has * taken place; we know that once this has happened, our vnode * has been replaced in, so we can return successfully. */ 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); 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)); } /* * Drain the audit queue and close the log at shutdown. */ void audit_shutdown(void) { audit_rotate_vnode(NULL, NULL); } static __inline__ struct uthread * curuthread(void) { return (get_bsdthread_info(current_act())); } static __inline__ struct kaudit_record * currecord(void) { return (curuthread()->uu_ar); } /********************************** * Begin system calls. * **********************************/ /* * System call to allow a user space application to submit a BSM audit * record to the kernel for inclusion in the audit log. This function * does little verification on the audit record that is submitted. * * XXXAUDIT: Audit preselection for user records does not currently * 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) { register struct pcred *pc = p->p_cred; int error; void * rec; struct kaudit_record *ar; 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 (ar == NULL) return (ENOTSUP); error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); 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); error = copyin(uap->record, rec, uap->length); if (error) goto free_out; /* Verify the record */ if (bsm_rec_verify(rec) == 0) { error = EINVAL; goto free_out; } /* Attach the user audit record to the kernel audit record. Because * this system call is an auditable event, we will write the user * record along with the record for this audit event. */ ar->k_udata = rec; ar->k_ulen = uap->length; return (0); free_out: kmem_free(kernel_map, (vm_offset_t)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) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); return (ENOSYS); } /* * 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; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); return (ENOSYS); } /* * 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) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); error = copyout((void *)&p->p_au->ai_auid, (void *)uap->auid, sizeof(*uap->auid)); 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) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); error = copyin((void *)uap->auid, (void *)&p->p_au->ai_auid, sizeof(p->p_au->ai_auid)); if (error) return (error); audit_arg_auid(p->p_au->ai_auid); return (0); } /* * System calls to get and set process audit information. */ struct getaudit_args { struct auditinfo *auditinfo; }; /* ARGSUSED */ int getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); error = copyout((void *)p->p_au, (void *)uap->auditinfo, sizeof(*uap->auditinfo)); 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) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); error = copyin((void *)uap->auditinfo, (void *)p->p_au, sizeof(*p->p_au)); if (error) return (error); 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) { 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) { register struct pcred *pc = p->p_cred; int error; error = suser(pc->pc_ucred, &p->p_acflag); if (error) return (error); return (ENOSYS); } /* * 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) { struct kaudit_record *ar; struct nameidata nd; struct ucred *cred; struct vnode *vp; int error, flags, ret; error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); vp = NULL; cred = NULL; /* * If a path is specified, open the replacement vnode, perform * 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 (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); error = EINVAL; goto out; } cred = p->p_ucred; crhold(cred); } audit_rotate_vnode(cred, vp); out: return (error); } /********************************** * End of system calls. * **********************************/ /* * MPSAFE */ struct kaudit_record * audit_new(int event, struct proc *p, struct uthread *uthread) { struct kaudit_record *ar; int no_record; /* * Eventually, there may be certain classes of events that * we will audit regardless of the audit state at the time * the record is created. These events will generally * correspond to changes in the audit state. The dummy * code below is from our first prototype, but may also * be used in the final version (with modified event numbers). */ #if 0 if (event != AUDIT_EVENT_FILESTOP && event != AUDIT_EVENT_FILESTART) { #endif mutex_lock(audit_mtx); no_record = (audit_suspended || !audit_enabled); mutex_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. */ (void)kmem_alloc(kernel_map, &ar, sizeof(*ar)); if (ar == NULL) return NULL; bzero(ar, sizeof(*ar)); ar->k_ar.ar_magic = AUDIT_RECORD_MAGIC; ar->k_ar.ar_event = event; nanotime(&ar->k_ar.ar_starttime); /* 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; 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)); return (ar); } /* * MPSAFE * XXXAUDIT: So far, this is unused, and should probably be GC'd. */ void audit_abort(struct kaudit_record *ar) { audit_free(ar); } /* * MPSAFE */ void audit_commit(struct kaudit_record *ar, int error, int retval) { if (ar == NULL) return; ar->k_ar.ar_errno = error; ar->k_ar.ar_retval = retval; /* * We might want to do some system-wide post-filtering * here at some point. */ /* * Timestamp system call end. */ 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); /* * 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_free(ar); return; } TAILQ_INSERT_TAIL(&audit_q, ar, k_q); wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED); mutex_unlock(audit_mtx); } /* * Calls to set up and tear down audit structures associated with * each system call. */ void audit_syscall_enter(unsigned short code, struct proc *proc, struct uthread *uthread) { int audit_event; assert(uthread->uu_ar == NULL); audit_event = sys_au_event[code]; /* * Allocate an audit record, if desired, 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)) { uthread->uu_ar = audit_new(audit_event, proc, uthread); } else { uthread->uu_ar = NULL; } } } void audit_syscall_exit(int error, struct proc *proc, struct uthread *uthread) { int retval; /* * Commit the audit record as desired; once we pass the record * into audit_commit(), the memory is owned by the audit * subsystem. * The return value from the system call is stored on the user * thread. If there was an error, the return value is set to -1, * imitating the behavior of the cerror routine. */ if (error) retval = -1; else retval = uthread->uu_rval[0]; audit_commit(uthread->uu_ar, error, retval); if (uthread->uu_ar != NULL) AUDIT_PRINTF(("audit record committed by pid %d\n", proc->p_pid)); uthread->uu_ar = NULL; } /* * Calls to manipulate elements of the audit record structure from system * call code. Macro wrappers will prevent this functions from being * entered if auditing is disabled, avoiding the function call cost. We * check the thread audit record pointer anyway, as the audit condition * could change, and pre-selection may not have allocated an audit * record for this event. */ void audit_arg_accmode(int accmode) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_accmode = accmode; ar->k_ar.ar_valid_arg |= ARG_ACCMODE; } void audit_arg_cmode(int cmode) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_cmode = cmode; ar->k_ar.ar_valid_arg |= ARG_CMODE; } void audit_arg_fd(int fd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_fd = fd; ar->k_ar.ar_valid_arg |= ARG_FD; } void audit_arg_fflags(int fflags) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_fflags = fflags; ar->k_ar.ar_valid_arg |= ARG_FFLAGS; } void audit_arg_gid(gid_t gid, gid_t egid, gid_t rgid, gid_t sgid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_gid = gid; ar->k_ar.ar_arg_egid = egid; ar->k_ar.ar_arg_rgid = rgid; ar->k_ar.ar_arg_sgid = sgid; ar->k_ar.ar_valid_arg |= (ARG_GID | ARG_EGID | ARG_RGID | ARG_SGID); } void audit_arg_uid(uid_t uid, uid_t euid, uid_t ruid, uid_t suid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_uid = uid; ar->k_ar.ar_arg_euid = euid; ar->k_ar.ar_arg_ruid = ruid; ar->k_ar.ar_arg_suid = suid; ar->k_ar.ar_valid_arg |= (ARG_UID | ARG_EUID | ARG_RUID | ARG_SUID); } void audit_arg_groupset(gid_t *gidset, u_int gidset_size) { int i; struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; for (i = 0; i < gidset_size; i++) ar->k_ar.ar_arg_groups.gidset[i] = gidset[i]; ar->k_ar.ar_arg_groups.gidset_size = gidset_size; ar->k_ar.ar_valid_arg |= ARG_GROUPSET; } void audit_arg_login(char *login) { struct kaudit_record *ar; ar = currecord(); 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_mask(int mask) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_mask = mask; ar->k_ar.ar_valid_arg |= ARG_MASK; } void audit_arg_mode(mode_t mode) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_mode = mode; ar->k_ar.ar_valid_arg |= ARG_MODE; } void audit_arg_dev(int dev) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_dev = dev; ar->k_ar.ar_valid_arg |= ARG_DEV; } void audit_arg_owner(uid_t uid, gid_t gid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_uid = uid; ar->k_ar.ar_arg_gid = gid; ar->k_ar.ar_valid_arg |= (ARG_UID | ARG_GID); } void audit_arg_pid(pid_t pid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_pid = pid; ar->k_ar.ar_valid_arg |= ARG_PID; } void audit_arg_signum(u_int signum) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_signum = signum; ar->k_ar.ar_valid_arg |= ARG_SIGNUM; } void audit_arg_socket(int sodomain, int sotype, int soprotocol) { struct kaudit_record *ar; ar = currecord(); 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_valid_arg |= ARG_SOCKINFO; } void audit_arg_sockaddr(struct proc *p, struct sockaddr *so) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL || p == NULL || so == NULL) return; bcopy(so, &ar->k_ar.ar_arg_sockaddr, sizeof(ar->k_ar.ar_arg_sockaddr)); switch (so->sa_family) { case AF_INET: ar->k_ar.ar_valid_arg |= ARG_SADDRINET; break; case AF_INET6: ar->k_ar.ar_valid_arg |= ARG_SADDRINET6; break; case AF_UNIX: audit_arg_upath(p, ((struct sockaddr_un *)so)->sun_path, ARG_UPATH1); ar->k_ar.ar_valid_arg |= ARG_SADDRUNIX; break; } } void audit_arg_auid(uid_t auid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_auid = auid; ar->k_ar.ar_valid_arg |= ARG_AUID; } void audit_arg_text(char *text) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; /* Invalidate the text string */ ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_TEXT); if (text == NULL) return; if (ar->k_ar.ar_arg_text == NULL) { kmem_alloc(kernel_map, &ar->k_ar.ar_arg_text, MAXPATHLEN); if (ar->k_ar.ar_arg_text == NULL) return; } strcpy(ar->k_ar.ar_arg_text, text); ar->k_ar.ar_valid_arg |= ARG_TEXT; } void audit_arg_cmd(int cmd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_cmd = cmd; ar->k_ar.ar_valid_arg |= ARG_CMD; } void audit_arg_svipc_cmd(int cmd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_cmd = cmd; ar->k_ar.ar_valid_arg |= ARG_SVIPC_CMD; } void audit_arg_svipc_perm(struct ipc_perm *perm) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; bcopy(perm, &ar->k_ar.ar_arg_svipc_perm, sizeof(ar->k_ar.ar_arg_svipc_perm)); ar->k_ar.ar_valid_arg |= ARG_SVIPC_PERM; } void audit_arg_svipc_id(int id) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_id = id; ar->k_ar.ar_valid_arg |= ARG_SVIPC_ID; } void audit_arg_svipc_addr(void * addr) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_addr = 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) { MALLOC_ZONE(p->p_au, struct auditinfo *, sizeof(*p->p_au), M_SUBPROC, M_WAITOK); bzero((void *)p->p_au, sizeof(*p->p_au)); } /* * 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) { /* 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); bcopy(parent->p_au, child->p_au, sizeof(*child->p_au)); } /* * Free the auditing structure for the process. */ void audit_proc_free(struct proc *p) { FREE_ZONE((void *)p->p_au, sizeof(*p->p_au), M_SUBPROC); p->p_au = NULL; } /* * 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. */ void audit_arg_upath(struct proc *p, char *upath, u_int64_t flags) { struct kaudit_record *ar; char **pathp; if (p == NULL || upath == NULL) return; /* nothing to do! */ if (flags & (ARG_UPATH1 | ARG_UPATH2) == 0) return; ar = currecord(); if (ar == NULL) /* This will be the case for unaudited system calls */ return; if (flags & ARG_UPATH1) { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_UPATH1); pathp = &ar->k_ar.ar_arg_upath1; } else { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_UPATH2); pathp = &ar->k_ar.ar_arg_upath2; } if (*pathp == NULL) { kmem_alloc(kernel_map, pathp, 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; } /* * Function to save the path and vnode attr information into the audit * record. * * It is assumed that the caller will hold any vnode locks necessary to * perform a VOP_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 * to construct the BSM file ID. * XXX: We should accept the process argument from the caller, since * it's very likely they already have a reference. * XXX: Error handling in this function is poor. */ void audit_arg_vnpath(struct vnode *vp, u_int64_t flags) { struct kaudit_record *ar; struct vattr vattr; int error; int len; char **pathp; struct vnode_au_info *vnp; struct proc *p; if (vp == NULL) return; ar = currecord(); if (ar == NULL) /* This will be the case for unaudited system calls */ return; if (flags & (ARG_VNODE1 | ARG_VNODE2) == 0) return; p = current_proc(); if (flags & ARG_VNODE1) { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_KPATH1); ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE1); pathp = &ar->k_ar.ar_arg_kpath1; vnp = &ar->k_ar.ar_arg_vnode1; } 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 (*pathp == NULL) { kmem_alloc(kernel_map, pathp, MAXPATHLEN); if (*pathp == NULL) return; } /* Copy the path looked up by the vn_getpath() function */ len = MAXPATHLEN; vn_getpath(vp, *pathp, &len); if (flags & ARG_VNODE1) ar->k_ar.ar_valid_arg |= ARG_KPATH1; else ar->k_ar.ar_valid_arg |= ARG_KPATH2; /* * 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); 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 (flags & ARG_VNODE1) ar->k_ar.ar_valid_arg |= ARG_VNODE1; else ar->k_ar.ar_valid_arg |= ARG_VNODE2; } #else /* !AUDIT */ void audit_init(void) { } void audit_shutdown(void) { } int audit(struct proc *p, struct audit_args *uap, register_t *retval) { return (ENOSYS); } int auditon(struct proc *p, struct auditon_args *uap, register_t *retval) { return (ENOSYS); } int auditsvc(struct proc *p, struct auditsvc_args *uap, register_t *retval) { return (ENOSYS); } int getauid(struct proc *p, struct getauid_args *uap, register_t *retval) { return (ENOSYS); } int setauid(struct proc *p, struct setauid_args *uap, register_t *retval) { return (ENOSYS); } int getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval) { return (ENOSYS); } int setaudit(struct proc *p, struct setaudit_args *uap, register_t *retval) { return (ENOSYS); } int getaudit_addr(struct proc *p, struct getaudit_addr_args *uap, register_t *retval) { return (ENOSYS); } int setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval) { return (ENOSYS); } int auditctl(struct proc *p, struct auditctl_args *uap, register_t *retval) { return (ENOSYS); } void audit_proc_init(struct proc *p) { } void audit_proc_fork(struct proc *parent, struct proc *child) { } void audit_proc_free(struct proc *p) { } #endif /* AUDIT */