/*- * Copyright (c) 2001 Networks Associates Technology, Inc. * Copyright (c) 1982, 1986, 1989, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * This software was developed for the FreeBSD Project by NAI Labs, the * Security Research Division of Network Associates, Inc. under * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA * CHATS research program. * * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id$ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kernel_interface.h" #include "kernel_util.h" #include "kernel_mediate.h" #include "kernel_monitor.h" #include "lomacfs.h" #include "syscall_gate.h" #define AS(name) (sizeof(struct name) / sizeof(register_t)) int each_proc(int (*iter)(struct proc *p)) { struct proc *p; int error = 0; sx_slock(&allproc_lock); LIST_FOREACH(p, &allproc, p_list) { error = (*iter)(p); if (error) goto out; } out: sx_sunlock(&allproc_lock); return (error); } static int initialize_proc(struct proc *p) { lattr_t lattr = { LOMAC_HIGHEST_LEVEL, 0 }; init_subject_lattr(p, &lattr); return (0); } static void lomac_at_fork(struct proc *parent, struct proc *p, int flags) { if ((flags & RFMEM) == 0) { lattr_t parent_lattr; get_subject_lattr(parent, &parent_lattr); init_subject_lattr(p, &parent_lattr); } } static int lomac_proc_candebug(struct proc *p1, struct proc *p2) { lattr_t lattr; get_subject_lattr(p1, &lattr); if (mediate_subject_level_subject("debug", p1, lattr.level, p2)) return (0); else return (EPERM); } static int lomac_proc_cansched(struct proc *p1, struct proc *p2) { lattr_t lattr; get_subject_lattr(p1, &lattr); if (mediate_subject_level_subject("sched", p1, lattr.level, p2)) return (0); else return (EPERM); } static int lomac_proc_cansignal(struct proc *p1, struct proc *p2, int signum) { lattr_t lattr; get_subject_lattr(p1, &lattr); /* * Always allow signals to init(8) (necessary to shut down). */ if (p2->p_pid == 1 || mediate_subject_level_subject("signal", p1, lattr.level, p2)) return (0); else return (EPERM); } int lomac_initialize_procs(void) { int error; #ifdef P_CAN_HOOKS can_hooks_lock(); (void)p_candebug_hook(lomac_proc_candebug); (void)p_cansignal_hook(lomac_proc_cansignal); (void)p_cansched_hook(lomac_proc_cansched); can_hooks_unlock(); #endif error = at_fork(lomac_at_fork); if (error) return (error); return (each_proc(&initialize_proc)); } int lomac_uninitialize_procs(void) { rm_at_fork(lomac_at_fork); return (0); } extern int (*old_execve)(struct thread *, struct execve_args *); int execve(struct thread *td, struct execve_args *uap) { lattr_t lattr, textattr; struct vmspace *oldvmspace; struct proc *p; int error; p = td->td_proc; get_subject_lattr(p, &lattr); oldvmspace = p->p_vmspace; error = old_execve(td, uap); if (error == 0) { lomac_object_t lobj; lobj.lo_type = VISLOMAC(p->p_textvp) ? LO_TYPE_LVNODE : LO_TYPE_UVNODE; lobj.lo_object.vnode = p->p_textvp; get_object_lattr(&lobj, &textattr); /* * Install the executable's relevant attributes into the * process. */ lattr.flags |= textattr.flags & (LOMAC_ATTR_NODEMOTE | LOMAC_ATTR_NONETDEMOTE); if (p->p_vmspace != oldvmspace) init_subject_lattr(p, &lattr); else set_subject_lattr(p, lattr); mtx_lock(&Giant); (void)monitor_read_object(p, &lobj); mtx_unlock(&Giant); } return (error); } const char *linker_basename(const char* path); int linker_load_module(const char *kldname, const char *modname, struct linker_file *parent, struct mod_depend *verinfo, struct linker_file **lfpp); MALLOC_DECLARE(M_LINKER); /* * MPSAFE */ int kldload(struct thread* td, struct kldload_args* uap) { char *kldname, *modname; char *pathname = NULL; linker_file_t lf; int error = 0; td->td_retval[0] = -1; if (securelevel > 0) /* redundant, but that's OK */ return EPERM; mtx_lock(&Giant); if ((error = suser_td(td)) != 0) goto out; pathname = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); if ((error = copyinstr(SCARG(uap, file), pathname, MAXPATHLEN, NULL)) != 0) goto out; if (!mediate_subject_at_level("kldload", td->td_proc, LOMAC_HIGHEST_LEVEL)) { error = EPERM; goto out; } /* * If path do not contain qualified name or any dot in it (kldname.ko, or * kldname.ver.ko) treat it as interface name. */ if (index(pathname, '/') || index(pathname, '.')) { kldname = pathname; modname = NULL; } else { kldname = NULL; modname = pathname; } error = linker_load_module(kldname, modname, NULL, NULL, &lf); if (error) goto out; lf->userrefs++; td->td_retval[0] = lf->id; out: if (pathname) free(pathname, M_TEMP); mtx_unlock(&Giant); return (error); } #ifdef __i386__ #include extern int (*old_sysarch)(struct thread *, void *); int sysarch(struct thread *td, struct sysarch_args *uap) { switch (uap->op) { case I386_SET_IOPERM: if (!mediate_subject_at_level("ioperm", td->td_proc, LOMAC_HIGHEST_LEVEL)) return (EPERM); default: return (old_sysarch(td, uap)); } } #endif extern int lomac_mmap(struct proc *, struct mmap_args *); /* * Mount a file system. */ #ifndef _SYS_SYSPROTO_H_ struct mount_args { char *type; char *path; int flags; caddr_t data; }; #endif /* ARGSUSED */ int mount(td, uap) struct thread *td; struct mount_args /* { syscallarg(char *) type; syscallarg(char *) path; syscallarg(int) flags; syscallarg(caddr_t) data; } */ *uap; { char *fstype; char *fspath; int error; fstype = malloc(MFSNAMELEN, M_TEMP, M_WAITOK | M_ZERO); fspath = malloc(MNAMELEN, M_TEMP, M_WAITOK | M_ZERO); /* * vfs_mount() actually takes a kernel string for `type' and * `path' now, so extract them. */ error = copyinstr(SCARG(uap, type), fstype, MFSNAMELEN, NULL); if (error) goto finish; error = copyinstr(SCARG(uap, path), fspath, MNAMELEN, NULL); if (error) goto finish; if (!mediate_subject_at_level("mount", td->td_proc, LOMAC_HIGHEST_LEVEL)) { error = EPERM; goto finish; } error = vfs_mount(td, fstype, fspath, SCARG(uap, flags), SCARG(uap, data)); finish: free(fstype, M_TEMP); free(fspath, M_TEMP); return (error); } /* * Unmount a file system. * * Note: unmount takes a path to the vnode mounted on as argument, * not special file (as before). */ #ifndef _SYS_SYSPROTO_H_ struct unmount_args { char *path; int flags; }; #endif /* ARGSUSED */ int unmount(td, uap) struct thread *td; register struct unmount_args /* { syscallarg(char *) path; syscallarg(int) flags; } */ *uap; { register struct vnode *vp; struct mount *mp; int error; struct nameidata nd; NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, SCARG(uap, path), td); if ((error = namei(&nd)) != 0) return (error); vp = nd.ni_vp; NDFREE(&nd, NDF_ONLY_PNBUF); mp = vp->v_mount; /* * Only root, or the user that did the original mount is * permitted to unmount this filesystem. */ if (!mediate_subject_at_level("unmount", td->td_proc, LOMAC_HIGHEST_LEVEL) || ((mp->mnt_stat.f_owner != td->td_ucred->cr_uid) && (error = suser_td(td)))) { vput(vp); return (error); } /* * Don't allow unmounting the root file system. */ if (mp->mnt_flag & MNT_ROOTFS) { vput(vp); return (EINVAL); } /* * Must be the root of the filesystem */ if ((vp->v_flag & VROOT) == 0) { vput(vp); return (EINVAL); } vput(vp); return (dounmount(mp, SCARG(uap, flags), td)); } static struct syscall_override { int offset; sy_call_t *call; int narg; int mpsafe; } syscall_overrides[] = { { SYS_mmap, (sy_call_t *)mmap, AS(mmap_args), 1 }, { SYS_execve, (sy_call_t *)execve, AS(execve_args), 1 }, { SYS_kldload, (sy_call_t *)kldload, AS(kldload_args), 1 }, { SYS_mount, (sy_call_t *)mount, AS(mount_args), 0 }, { SYS_unmount, (sy_call_t *)unmount, AS(unmount_args), 0 }, #ifdef __i386__ { SYS_sysarch, (sy_call_t *)sysarch, AS(sysarch_args), 1 } #endif }; int lomac_initialize_syscalls(void) { int error, i; for (i = 0; i < sizeof(syscall_overrides) / sizeof(syscall_overrides[0]); i++) { struct syscall_override *so = &syscall_overrides[i]; error = syscall_gate_register(so->offset, so->call, so->narg, so->mpsafe); if (error) { while (--i >= 0) syscall_gate_deregister( syscall_overrides[i].offset); return (error); } } return (0); } int lomac_uninitialize_syscalls(void) { int i; for (i = 0; i < sizeof(syscall_overrides) / sizeof(syscall_overrides[0]); i++) syscall_gate_deregister(syscall_overrides[i].offset); return (0); } /* This memory is shared by all lomac_do_recwd() calls, in sequence. */ static char *pathmem; #define DIRENTMEM_SIZE (64 << 10) /* 64KB is good, I guess! */ static char *direntmem; static int lomac_dirents_searchbyid(struct vnode *dvp, struct dirent *dp, struct dirent *enddp, const struct vattr *vap, struct dirent **retdp) { struct vattr pvattr; struct componentname cnp; struct thread *td = curthread; struct ucred *ucred = td->td_ucred; struct vnode *vp; int error; *retdp = NULL; for (; dp != enddp; dp = (struct dirent *)((char *)dp + dp->d_reclen)) { cnp.cn_nameiop = LOOKUP; cnp.cn_flags = LOCKPARENT | ISLASTCN | NOFOLLOW; cnp.cn_thread = td; cnp.cn_cred = ucred; cnp.cn_nameptr = dp->d_name; cnp.cn_namelen = dp->d_namlen; error = VOP_LOOKUP(dvp, &vp, &cnp); if (error) return (error); error = VOP_GETATTR(vp, &pvattr, ucred, td); if (vp != dvp) (void)vput(vp); else vrele(vp); /* if looking up "." */ if (error) return (error); if (pvattr.va_fsid == vap->va_fsid && pvattr.va_fileid == vap->va_fileid) { *retdp = dp; break; } } return (0); } static int lomac_getcwd( struct thread *td, char *buf, size_t buflen, char **bufret ) { struct vattr cvattr; char *bp; int error, i, slash_prefixed; struct filedesc *fdp; struct vnode *vp, *startvp, *dvp; if (buflen < 2) return (EINVAL); if (buflen > MAXPATHLEN) buflen = MAXPATHLEN; bp = buf; bp += buflen - 1; *bp = '\0'; fdp = td->td_proc->p_fd; slash_prefixed = 0; #if defined(LOMAC_DEBUG_RECWD) printf("lomac_getcwd for %d:\n", td->td_proc->p_pid); #endif startvp = fdp->fd_cdir; vref(startvp); for (vp = startvp; vp != rootvnode; vp = dvp) { struct iovec diov = { direntmem, DIRENTMEM_SIZE }; struct uio duio = { &diov, 1, 0, DIRENTMEM_SIZE, UIO_SYSSPACE, UIO_READ, curthread }; struct dirent *dp; int direof; if (vp->v_flag & VROOT) { if (vp->v_mount == NULL) /* forced unmount */ return (EBADF); dvp = vp->v_mount->mnt_vnodecovered; continue; } dvp = vp->v_dd; if (vp == dvp) break; /* * Utilize POSIX requirement of files having same * st_dev and st_ino to be the same file, in our * case with vattr.va_fsid and vattr.va_fileid. */ error = vget(vp, LK_EXCLUSIVE, curthread); if (error) goto out2; error = VOP_GETATTR(vp, &cvattr, curthread->td_ucred, curthread); if (error) goto out2; (void)vput(vp); error = vget(dvp, LK_EXCLUSIVE, curthread); if (error) goto out2; for (direof = 0; !direof;) { error = VOP_READDIR(dvp, &duio, curthread->td_ucred, &direof, NULL, NULL); if (error) break; error = lomac_dirents_searchbyid(dvp, (struct dirent *)direntmem, (struct dirent *)(direntmem + DIRENTMEM_SIZE - duio.uio_resid), &cvattr, &dp); if (error) break; if (dp != NULL) { (void)vput(dvp); #if defined(LOMAC_DEBUG_RECWD) printf("\tdirent component: \"%.*s\"\n", dp->d_namlen, dp->d_name); #endif for (i = dp->d_namlen - 1; i >= 0; i--) if (bp == buf) return (ENOMEM); else *--bp = dp->d_name[i]; goto nextcomp; } diov.iov_base = direntmem; diov.iov_len = DIRENTMEM_SIZE; duio.uio_resid = DIRENTMEM_SIZE; } if (direof) error = ENOENT; (void)vput(dvp); out2: #if defined(LOMAC_DEBUG_RECWD) printf("backup dirent lookup problem: %d\n", error); #endif goto out; nextcomp: if (bp == buf) return (ENOMEM); *--bp = '/'; slash_prefixed = 1; } if (!slash_prefixed) { if (bp == buf) return (ENOMEM); *--bp = '/'; } error = 0; *bufret = bp; out: vrele(startvp); return (error); } static int lomac_do_recwd(struct proc *p) { struct nameidata nd; struct filedesc *fdp = curthread->td_proc->p_fd; struct thread *td = FIRST_THREAD_IN_PROC(p); /* XXXKSE Only one? */ char *nbuf; struct vnode *cdir, *rdir, *vp; int error; if (p == curthread->td_proc) return (0); PROC_LOCK(p); if (p->p_flag & P_SYSTEM) { PROC_UNLOCK(p); return (0); } PROC_UNLOCK(p); error = lomac_getcwd(td, pathmem, MAXPATHLEN, &nbuf); if (error) { #if defined(LOMAC_DEBUG_RECWD) printf("lomac: recwd() failure, lomac_getcwd() == %d\n", error); #endif return (0); } rdir = fdp->fd_rdir; fdp->fd_rdir = rootvnode; vref(fdp->fd_rdir); NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, nbuf, curthread); error = namei(&nd); vrele(fdp->fd_rdir); fdp->fd_rdir = rdir; if (error == 0) { vp = nd.ni_vp; if (vp->v_type != VDIR) error = ENOTDIR; else error = VOP_ACCESS(vp, VEXEC, td->td_ucred, curthread); if (error) vput(vp); else { NDFREE(&nd, NDF_ONLY_PNBUF); fdp = p->p_fd; cdir = fdp->fd_cdir; fdp->fd_cdir = vp; vrele(cdir); VOP_UNLOCK(vp, 0, curthread); } } #if defined(LOMAC_DEBUG_RECWD) printf("\trecwd() to \"%.*s\" == %d\n", MAXPATHLEN, nbuf, error); #endif return (0); } int lomac_initialize_cwds(void) { int error; pathmem = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); direntmem = malloc(DIRENTMEM_SIZE, M_TEMP, M_WAITOK); mtx_lock(&Giant); error = each_proc(lomac_do_recwd); mtx_unlock(&Giant); free(pathmem, M_TEMP); free(direntmem, M_TEMP); return (error); }