freebsd-nq/sys/security/lomac/kernel_util.c
Julian Elischer 079b7badea Pre-KSE/M3 commit.
this is a low-functionality change that changes the kernel to access the main
thread of a process via the linked list of threads rather than
assuming that it is embedded in the process. It IS still embeded there
but remove all teh code that assumes that in preparation for the next commit
which will actually move it out.

Reviewed by: peter@freebsd.org, gallatin@cs.duke.edu, benno rice,
2002-02-07 20:58:47 +00:00

697 lines
16 KiB
C

/*-
* Copyright (c) 2001 Networks Associates Technologies, 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 <sys/param.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/signalvar.h>
#include <sys/sx.h>
#include <sys/syscall.h>
#include <sys/sysent.h>
#include <sys/sysproto.h>
#include <sys/systm.h>
#include <sys/linker.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/dirent.h>
#include <sys/namei.h>
#include <sys/uio.h>
#include <sys/unistd.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#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 <machine/sysarch.h>
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_proc->p_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_proc->p_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);
}