1884 lines
42 KiB
C
1884 lines
42 KiB
C
/* $NetBSD: kern_systrace.c,v 1.75 2007/09/25 14:04:07 ad Exp $ */
|
|
|
|
/*
|
|
* Copyright 2002, 2003 Niels Provos <provos@citi.umich.edu>
|
|
* All rights reserved.
|
|
*
|
|
* 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Niels Provos.
|
|
* 4. 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 ``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 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: kern_systrace.c,v 1.75 2007/09/25 14:04:07 ad Exp $");
|
|
|
|
#include "opt_systrace.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/tree.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/device.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/file.h>
|
|
#include <sys/filedesc.h>
|
|
#include <sys/filio.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/pool.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/systrace.h>
|
|
#include <sys/kauth.h>
|
|
|
|
#include <compat/common/compat_util.h>
|
|
|
|
#define SYSTRACE_LOCK(fst, p) mutex_enter(&fst->mutex)
|
|
#define SYSTRACE_UNLOCK(fst, p) mutex_exit(&fst->mutex)
|
|
|
|
#ifndef M_XDATA
|
|
MALLOC_DEFINE(M_SYSTR, "systrace", "systrace");
|
|
#define M_XDATA M_SYSTR
|
|
#endif
|
|
|
|
#ifdef __NetBSD__
|
|
dev_type_open(systraceopen);
|
|
#else
|
|
cdev_decl(systrace);
|
|
#endif
|
|
|
|
#ifdef __NetBSD__
|
|
int systracef_read(struct file *, off_t *, struct uio *, kauth_cred_t,
|
|
int);
|
|
int systracef_write(struct file *, off_t *, struct uio *, kauth_cred_t,
|
|
int);
|
|
int systracef_poll(struct file *, int, struct lwp *);
|
|
int systracef_ioctl(struct file *, u_long, void *, struct lwp *);
|
|
int systracef_close(struct file *, struct lwp *);
|
|
#else
|
|
int systracef_read(struct file *, off_t *, struct uio *, kauth_cred_t);
|
|
int systracef_write(struct file *, off_t *, struct uio *, kauth_cred_t);
|
|
int systracef_select(struct file *, int, struct proc *);
|
|
int systracef_ioctl(struct file *, u_long, void *, struct proc *);
|
|
int systracef_stat(struct file *, struct stat *, struct proc *);
|
|
int systracef_close(struct file *, struct proc *);
|
|
#endif
|
|
|
|
struct str_policy {
|
|
TAILQ_ENTRY(str_policy) next;
|
|
|
|
int nr;
|
|
|
|
const struct emul *emul; /* Is only valid for this emulation */
|
|
|
|
int refcount;
|
|
|
|
int nsysent;
|
|
u_char *sysent;
|
|
};
|
|
|
|
#define STR_PROC_ONQUEUE 0x01
|
|
#define STR_PROC_WAITANSWER 0x02
|
|
#define STR_PROC_SYSCALLRES 0x04
|
|
#define STR_PROC_REPORT 0x08 /* Report emulation */
|
|
#define STR_PROC_NEEDSEQNR 0x10 /* Answer must quote seqnr */
|
|
#define STR_PROC_SETEUID 0x20 /* Elevate privileges */
|
|
#define STR_PROC_SETEGID 0x40
|
|
#define STR_PROC_DIDSETUGID 0x80
|
|
|
|
struct str_process {
|
|
TAILQ_ENTRY(str_process) next;
|
|
|
|
struct proc *proc;
|
|
const struct emul *oldemul;
|
|
uid_t olduid;
|
|
gid_t oldgid;
|
|
|
|
pid_t pid;
|
|
|
|
struct fsystrace *parent;
|
|
struct str_policy *policy;
|
|
|
|
struct systrace_replace *replace;
|
|
char *fname[SYSTR_MAXFNAME];
|
|
size_t nfname;
|
|
|
|
int flags;
|
|
short answer;
|
|
short error;
|
|
uint16_t seqnr; /* expected reply sequence number */
|
|
|
|
uid_t seteuid;
|
|
uid_t saveuid;
|
|
gid_t setegid;
|
|
gid_t savegid;
|
|
|
|
int isscript;
|
|
char scriptname[MAXPATHLEN];
|
|
};
|
|
|
|
uid_t systrace_seteuid(struct lwp *, uid_t);
|
|
gid_t systrace_setegid(struct lwp *, gid_t);
|
|
void systrace_lock(void);
|
|
void systrace_unlock(void);
|
|
|
|
/* Needs to be called with fst locked */
|
|
|
|
int systrace_attach(struct fsystrace *, pid_t);
|
|
int systrace_detach(struct str_process *);
|
|
int systrace_answer(struct str_process *, struct systrace_answer *);
|
|
int systrace_setscriptname(struct str_process *, struct
|
|
systrace_scriptname *);
|
|
int systrace_io(struct str_process *, struct systrace_io *);
|
|
int systrace_policy(struct fsystrace *, struct systrace_policy *);
|
|
int systrace_preprepl(struct str_process *, struct systrace_replace *);
|
|
int systrace_replace(struct str_process *, size_t, register_t []);
|
|
int systrace_getcwd(struct fsystrace *, struct str_process *);
|
|
int systrace_fname(struct str_process *, void *, size_t);
|
|
void systrace_replacefree(struct str_process *);
|
|
|
|
int systrace_processready(struct str_process *);
|
|
struct proc *systrace_find(struct str_process *);
|
|
struct str_process *systrace_findpid(struct fsystrace *fst, pid_t pid);
|
|
void systrace_wakeup(struct fsystrace *);
|
|
void systrace_closepolicy(struct fsystrace *, struct str_policy *);
|
|
int systrace_insert_process(struct fsystrace *, struct proc *,
|
|
struct str_process **);
|
|
struct str_policy *systrace_newpolicy(struct fsystrace *, int);
|
|
int systrace_msg_child(struct fsystrace *, struct str_process *, pid_t);
|
|
int systrace_msg_policyfree(struct fsystrace *, struct str_policy *);
|
|
int systrace_msg_ask(struct fsystrace *, struct str_process *,
|
|
int, size_t, register_t []);
|
|
int systrace_msg_result(struct fsystrace *, struct str_process *,
|
|
int, int, size_t, register_t [], register_t []);
|
|
int systrace_msg_emul(struct fsystrace *, struct str_process *);
|
|
int systrace_msg_ugid(struct fsystrace *, struct str_process *);
|
|
int systrace_make_msg(struct str_process *, int, struct str_message *);
|
|
|
|
static const struct fileops systracefops = {
|
|
systracef_read,
|
|
systracef_write,
|
|
systracef_ioctl,
|
|
fnullop_fcntl,
|
|
systracef_poll,
|
|
fbadop_stat,
|
|
systracef_close,
|
|
fnullop_kqfilter
|
|
};
|
|
|
|
#ifdef __NetBSD__
|
|
POOL_INIT(systr_proc_pl, sizeof(struct str_process), 0, 0, 0, "strprocpl",
|
|
NULL, IPL_NONE);
|
|
POOL_INIT(systr_policy_pl, sizeof(struct str_policy), 0, 0, 0, "strpolpl",
|
|
NULL, IPL_NONE);
|
|
POOL_INIT(systr_msgcontainer_pl, sizeof(struct str_msgcontainer), 0, 0, 0,
|
|
"strmsgpl", NULL, IPL_NONE);
|
|
|
|
kmutex_t systrace_mutex;
|
|
#else /* ! __NetBSD__ */
|
|
struct pool systr_proc_pl;
|
|
struct pool systr_policy_pl;
|
|
struct pool systr_msgcontainer_pl;
|
|
|
|
struct lock systrace_lck;
|
|
#endif /* __NetBSD__ */
|
|
|
|
int systrace_debug = 0;
|
|
|
|
#ifdef __NetBSD__
|
|
const struct cdevsw systrace_cdevsw = {
|
|
systraceopen, noclose, noread, nowrite, noioctl,
|
|
nostop, notty, nopoll, nommap, nokqfilter, D_OTHER,
|
|
};
|
|
#endif
|
|
|
|
#define DPRINTF(y) if (systrace_debug) printf y;
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
systracef_read(struct file *fp, off_t *poff, struct uio *uio, kauth_cred_t cred,
|
|
int flags)
|
|
{
|
|
struct fsystrace *fst = (struct fsystrace *)fp->f_data;
|
|
struct str_msgcontainer *cont;
|
|
int error = 0;
|
|
|
|
if (uio->uio_resid != sizeof(struct str_message))
|
|
return (EINVAL);
|
|
|
|
again:
|
|
systrace_lock();
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
if ((cont = TAILQ_FIRST(&fst->messages)) != NULL) {
|
|
error = uiomove((void *)&cont->msg,
|
|
sizeof(struct str_message), uio);
|
|
if (!error) {
|
|
TAILQ_REMOVE(&fst->messages, cont, next);
|
|
if (!SYSTR_MSG_NOPROCESS(cont))
|
|
CLR(cont->strp->flags, STR_PROC_ONQUEUE);
|
|
pool_put(&systr_msgcontainer_pl, cont);
|
|
|
|
}
|
|
} else if (TAILQ_FIRST(&fst->processes) == NULL) {
|
|
/* EOF situation */
|
|
;
|
|
} else {
|
|
if (fp->f_flag & FNONBLOCK)
|
|
error = EAGAIN;
|
|
else {
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
error = tsleep(fst, PWAIT|PCATCH, "systrrd", 0);
|
|
if (error)
|
|
goto out;
|
|
goto again;
|
|
}
|
|
|
|
}
|
|
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
out:
|
|
return (error);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
systracef_write(struct file *fp, off_t *poff, struct uio *uio,
|
|
kauth_cred_t cred, int flags)
|
|
{
|
|
|
|
return (EIO);
|
|
}
|
|
|
|
#define POLICY_VALID(x) ((x) == SYSTR_POLICY_PERMIT || \
|
|
(x) == SYSTR_POLICY_ASK || \
|
|
(x) == SYSTR_POLICY_NEVER)
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
systracef_ioctl(struct file *fp, u_long cmd, void *data, struct lwp *l)
|
|
{
|
|
int ret = 0;
|
|
struct fsystrace *fst = fp->f_data;
|
|
#ifdef __NetBSD__
|
|
struct proc *p = l->l_proc;
|
|
struct cwdinfo *cwdp;
|
|
#else
|
|
struct filedesc *fdp;
|
|
#endif
|
|
struct str_process *strp = NULL;
|
|
pid_t pid = 0;
|
|
|
|
switch (cmd) {
|
|
case FIONBIO:
|
|
case FIOASYNC:
|
|
return (0);
|
|
|
|
case STRIOCDETACH:
|
|
case STRIOCREPORT:
|
|
case STRIOCGETCWD:
|
|
pid = *(pid_t *)data;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
break;
|
|
case STRIOCANSWER:
|
|
pid = ((struct systrace_answer *)data)->stra_pid;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
break;
|
|
case STRIOCIO:
|
|
pid = ((struct systrace_io *)data)->strio_pid;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
break;
|
|
case STRIOCSCRIPTNAME:
|
|
pid = ((struct systrace_scriptname *)data)->sn_pid;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
break;
|
|
case STRIOCATTACH:
|
|
case STRIOCRESCWD:
|
|
case STRIOCPOLICY:
|
|
break;
|
|
case STRIOCREPLACE:
|
|
pid = ((struct systrace_replace *)data)->strr_pid;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
break;
|
|
default:
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
return (ret);
|
|
|
|
systrace_lock();
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
if (pid) {
|
|
strp = systrace_findpid(fst, pid);
|
|
if (strp == NULL) {
|
|
ret = ESRCH;
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
switch (cmd) {
|
|
case STRIOCATTACH:
|
|
pid = *(pid_t *)data;
|
|
if (!pid)
|
|
ret = EINVAL;
|
|
else
|
|
ret = systrace_attach(fst, pid);
|
|
DPRINTF(("%s: attach to %u: %d\n", __func__, pid, ret));
|
|
break;
|
|
case STRIOCDETACH:
|
|
ret = systrace_detach(strp);
|
|
break;
|
|
case STRIOCREPORT:
|
|
SET(strp->flags, STR_PROC_REPORT);
|
|
break;
|
|
case STRIOCANSWER:
|
|
ret = systrace_answer(strp, (struct systrace_answer *)data);
|
|
break;
|
|
case STRIOCIO:
|
|
ret = systrace_io(strp, (struct systrace_io *)data);
|
|
break;
|
|
case STRIOCSCRIPTNAME:
|
|
ret = systrace_setscriptname(strp,
|
|
(struct systrace_scriptname *)data);
|
|
break;
|
|
case STRIOCPOLICY:
|
|
ret = systrace_policy(fst, (struct systrace_policy *)data);
|
|
break;
|
|
case STRIOCREPLACE:
|
|
ret = systrace_preprepl(strp, (struct systrace_replace *)data);
|
|
break;
|
|
case STRIOCRESCWD:
|
|
if (!fst->fd_pid) {
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
#ifdef __NetBSD__
|
|
cwdp = p->p_cwdi;
|
|
|
|
/* Release cwd from other process */
|
|
if (cwdp->cwdi_cdir)
|
|
vrele(cwdp->cwdi_cdir);
|
|
if (cwdp->cwdi_rdir)
|
|
vrele(cwdp->cwdi_rdir);
|
|
cwdp->cwdi_cdir = fst->fd_cdir;
|
|
cwdp->cwdi_rdir = fst->fd_rdir;
|
|
#else
|
|
fdp = p->p_fd;
|
|
|
|
/* Release cwd from other process */
|
|
if (fdp->fd_cdir)
|
|
vrele(fdp->fd_cdir);
|
|
if (fdp->fd_rdir)
|
|
vrele(fdp->fd_rdir);
|
|
/* This restores the cwd we had before */
|
|
fdp->fd_cdir = fst->fd_cdir;
|
|
fdp->fd_rdir = fst->fd_rdir;
|
|
#endif
|
|
/* Note that we are normal again */
|
|
fst->fd_pid = 0;
|
|
fst->fd_cdir = fst->fd_rdir = NULL;
|
|
break;
|
|
case STRIOCGETCWD:
|
|
ret = systrace_getcwd(fst, strp);
|
|
break;
|
|
default:
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (strp) {
|
|
struct proc *q = strp->proc;
|
|
|
|
mutex_enter(&q->p_mutex);
|
|
proc_delref(q);
|
|
mutex_exit(&q->p_mutex);
|
|
}
|
|
unlock:
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
#ifdef __NetBSD__
|
|
int
|
|
systracef_poll(struct file *fp, int events, struct lwp *l)
|
|
{
|
|
struct fsystrace *fst = (struct fsystrace *)fp->f_data;
|
|
int revents = 0;
|
|
|
|
if ((events & (POLLIN | POLLRDNORM)) == 0)
|
|
return (revents);
|
|
|
|
systrace_lock();
|
|
SYSTRACE_LOCK(fst, l->l_proc);
|
|
systrace_unlock();
|
|
if (!TAILQ_EMPTY(&fst->messages))
|
|
revents |= events & (POLLIN | POLLRDNORM);
|
|
if (revents == 0)
|
|
selrecord(l, &fst->si);
|
|
SYSTRACE_UNLOCK(fst, l->l_proc);
|
|
|
|
return (revents);
|
|
}
|
|
#else
|
|
int
|
|
systracef_select(struct file *fp, int which, struct proc *p)
|
|
{
|
|
struct fsystrace *fst = (struct fsystrace *)fp->f_data;
|
|
int ready = 0;
|
|
|
|
if (which != FREAD)
|
|
return (0);
|
|
|
|
systrace_lock();
|
|
SYSTRACE_LOCK(fst, p);
|
|
systrace_unlock();
|
|
ready = TAILQ_FIRST(&fst->messages) != NULL;
|
|
if (!ready)
|
|
selrecord(p, &fst->si);
|
|
SYSTRACE_UNLOCK(fst, p);
|
|
|
|
return (ready);
|
|
}
|
|
#endif /* __NetBSD__ */
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
systracef_close(struct file *fp, struct lwp *l)
|
|
{
|
|
struct fsystrace *fst = (struct fsystrace *)fp->f_data;
|
|
struct str_process *strp;
|
|
struct str_msgcontainer *cont;
|
|
struct str_policy *strpol;
|
|
|
|
systrace_lock();
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
/* Untrace all processes */
|
|
for (strp = TAILQ_FIRST(&fst->processes); strp;
|
|
strp = TAILQ_FIRST(&fst->processes)) {
|
|
struct proc *q = strp->proc;
|
|
|
|
systrace_detach(strp);
|
|
mutex_enter(&proclist_lock); /* XXXSMP */
|
|
psignal(q, SIGKILL);
|
|
mutex_exit(&proclist_lock); /* XXXSMP */
|
|
}
|
|
|
|
/* Clean up fork and exit messages */
|
|
for (cont = TAILQ_FIRST(&fst->messages); cont;
|
|
cont = TAILQ_FIRST(&fst->messages)) {
|
|
TAILQ_REMOVE(&fst->messages, cont, next);
|
|
pool_put(&systr_msgcontainer_pl, cont);
|
|
}
|
|
|
|
/* Clean up all policies */
|
|
for (strpol = TAILQ_FIRST(&fst->policies); strpol;
|
|
strpol = TAILQ_FIRST(&fst->policies))
|
|
systrace_closepolicy(fst, strpol);
|
|
|
|
/* Release vnodes */
|
|
if (fst->fd_cdir)
|
|
vrele(fst->fd_cdir);
|
|
if (fst->fd_rdir)
|
|
vrele(fst->fd_rdir);
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
|
|
seldestroy(&fst->si);
|
|
FREE(fp->f_data, M_XDATA);
|
|
fp->f_data = NULL;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
systrace_lock(void)
|
|
{
|
|
mutex_enter(&systrace_mutex);
|
|
}
|
|
|
|
void
|
|
systrace_unlock(void)
|
|
{
|
|
mutex_exit(&systrace_mutex);
|
|
}
|
|
|
|
void
|
|
systrace_init(void)
|
|
{
|
|
mutex_init(&systrace_mutex, MUTEX_DEFAULT, IPL_NONE);
|
|
}
|
|
|
|
int
|
|
systraceopen(dev_t dev, int flag, int mode, struct lwp *l)
|
|
{
|
|
struct fsystrace *fst;
|
|
struct file *fp;
|
|
int error, fd;
|
|
|
|
/* falloc() will use the descriptor for us. */
|
|
if ((error = falloc(l, &fp, &fd)) != 0)
|
|
return (error);
|
|
|
|
MALLOC(fst, struct fsystrace *, sizeof(*fst), M_XDATA, M_WAITOK);
|
|
|
|
memset(fst, 0, sizeof(struct fsystrace));
|
|
#ifdef __NetBSD__
|
|
mutex_init(&fst->mutex, MUTEX_DEFAULT, IPL_NONE);
|
|
#else
|
|
lockinit(&fst->lock, PLOCK, "systrace", 0, 0);
|
|
#endif
|
|
selinit(&fst->si);
|
|
TAILQ_INIT(&fst->processes);
|
|
TAILQ_INIT(&fst->messages);
|
|
TAILQ_INIT(&fst->policies);
|
|
|
|
if (kauth_authorize_generic(l->l_cred, KAUTH_GENERIC_ISSUSER,
|
|
NULL) == 0)
|
|
fst->issuser = 1;
|
|
fst->p_ruid = kauth_cred_getuid(l->l_cred);
|
|
fst->p_rgid = kauth_cred_getgid(l->l_cred);
|
|
|
|
return fdclone(l, fp, fd, flag, &systracefops, fst);
|
|
}
|
|
|
|
void
|
|
systrace_wakeup(struct fsystrace *fst)
|
|
{
|
|
wakeup((void *)fst);
|
|
selwakeup(&fst->si);
|
|
}
|
|
|
|
struct proc *
|
|
systrace_find(struct str_process *strp)
|
|
{
|
|
struct proc *proc;
|
|
int error;
|
|
|
|
mutex_enter(&proclist_lock);
|
|
|
|
if ((proc = p_find(strp->pid, PFIND_LOCKED)) == NULL) {
|
|
mutex_exit(&proclist_lock);
|
|
return (NULL);
|
|
}
|
|
|
|
mutex_enter(&proc->p_mutex);
|
|
if (proc != strp->proc || !ISSET(proc->p_flag, PK_SYSTRACE)) {
|
|
mutex_exit(&proc->p_mutex);
|
|
mutex_exit(&proclist_lock);
|
|
return (NULL);
|
|
}
|
|
error = proc_addref(proc);
|
|
mutex_exit(&proc->p_mutex);
|
|
mutex_exit(&proclist_lock);
|
|
|
|
return (error ? NULL : proc);
|
|
}
|
|
|
|
void
|
|
systrace_sys_exit(struct proc *proc)
|
|
{
|
|
struct str_process *strp;
|
|
struct fsystrace *fst;
|
|
|
|
systrace_lock();
|
|
strp = proc->p_systrace;
|
|
if (strp != NULL) {
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
/* Insert Exit message */
|
|
systrace_msg_child(fst, strp, -1);
|
|
|
|
systrace_detach(strp);
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
} else
|
|
systrace_unlock();
|
|
CLR(proc->p_flag, PK_SYSTRACE);
|
|
}
|
|
|
|
void
|
|
systrace_sys_fork(struct proc *oldproc, struct proc *p)
|
|
{
|
|
struct str_process *oldstrp, *strp;
|
|
struct fsystrace *fst;
|
|
|
|
systrace_lock();
|
|
oldstrp = oldproc->p_systrace;
|
|
if (oldstrp == NULL) {
|
|
systrace_unlock();
|
|
return;
|
|
}
|
|
|
|
fst = oldstrp->parent;
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
if (systrace_insert_process(fst, p, &strp)) {
|
|
/* We need to kill the child */
|
|
mutex_enter(&proclist_lock); /* XXXSMP */
|
|
psignal(p, SIGKILL);
|
|
mutex_exit(&proclist_lock); /* XXXSMP */
|
|
goto out;
|
|
}
|
|
|
|
/* Reference policy */
|
|
if ((strp->policy = oldstrp->policy) != NULL)
|
|
strp->policy->refcount++;
|
|
|
|
/* Insert fork message */
|
|
systrace_msg_child(fst, oldstrp, p->p_pid);
|
|
out:
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
}
|
|
|
|
int
|
|
systrace_enter(struct lwp *l, register_t code, void *v)
|
|
{
|
|
const struct sysent *callp;
|
|
struct str_process *strp;
|
|
struct str_policy *strpolicy;
|
|
struct fsystrace *fst;
|
|
struct proc *p = l->l_proc;
|
|
kauth_cred_t pc;
|
|
int policy, error = 0, maycontrol = 0, issuser = 0;
|
|
size_t argsize;
|
|
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp == NULL) {
|
|
systrace_unlock();
|
|
return (EINVAL);
|
|
}
|
|
|
|
KASSERT(strp->proc == p);
|
|
|
|
fst = strp->parent;
|
|
|
|
SYSTRACE_LOCK(fst, p);
|
|
systrace_unlock();
|
|
|
|
/*
|
|
* We can not monitor a SUID process unless we are root,
|
|
* but we wait until it executes something unprivileged.
|
|
* A non-root user may only monitor if the real uid and
|
|
* real gid match the monitored process. Changing the
|
|
* uid or gid causes P_SUGID to be set.
|
|
*/
|
|
if (fst->issuser) {
|
|
maycontrol = 1;
|
|
issuser = 1;
|
|
} else if (!(p->p_flag & PK_SUGID)) {
|
|
maycontrol = fst->p_ruid == kauth_cred_getuid(p->p_cred) &&
|
|
fst->p_rgid == kauth_cred_getgid(p->p_cred);
|
|
}
|
|
|
|
if (!maycontrol) {
|
|
policy = SYSTR_POLICY_PERMIT;
|
|
} else {
|
|
/* Find out current policy */
|
|
if ((strpolicy = strp->policy) == NULL)
|
|
policy = SYSTR_POLICY_ASK;
|
|
else {
|
|
if (code >= strpolicy->nsysent)
|
|
policy = SYSTR_POLICY_NEVER;
|
|
else
|
|
policy = strpolicy->sysent[code];
|
|
}
|
|
}
|
|
|
|
callp = p->p_emul->e_sysent + code;
|
|
|
|
/* Fast-path */
|
|
if (policy != SYSTR_POLICY_ASK) {
|
|
if (policy != SYSTR_POLICY_PERMIT) {
|
|
if (policy > 0)
|
|
error = policy;
|
|
else
|
|
error = EPERM;
|
|
}
|
|
strp->oldemul = NULL;
|
|
systrace_replacefree(strp);
|
|
SYSTRACE_UNLOCK(fst, p);
|
|
return (error);
|
|
}
|
|
|
|
/* Get the (adjusted) argsize */
|
|
argsize = callp->sy_argsize;
|
|
#ifdef _LP64
|
|
if (p->p_flag & PK_32)
|
|
argsize = argsize << 1;
|
|
#endif
|
|
|
|
/* Puts the current process to sleep, return unlocked */
|
|
error = systrace_msg_ask(fst, strp, code, argsize, v);
|
|
|
|
/* lock has been released in systrace_msg_ask() */
|
|
fst = NULL;
|
|
/* We might have detached by now for some reason */
|
|
if (!error && (strp = p->p_systrace) != NULL) {
|
|
/* XXX - do I need to lock here? */
|
|
if (strp->answer == SYSTR_POLICY_NEVER) {
|
|
error = strp->error;
|
|
systrace_replacefree(strp);
|
|
} else {
|
|
if (ISSET(strp->flags, STR_PROC_SYSCALLRES)) {
|
|
#ifndef __NetBSD__
|
|
CLR(strp->flags, STR_PROC_SYSCALLRES);
|
|
#endif
|
|
}
|
|
/* Replace the arguments if necessary */
|
|
if (strp->replace != NULL) {
|
|
error = systrace_replace(strp, argsize, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
systrace_lock();
|
|
if ((strp = p->p_systrace) == NULL)
|
|
goto out;
|
|
|
|
if (error) {
|
|
strp->oldemul = NULL;
|
|
goto out;
|
|
}
|
|
|
|
pc = p->p_cred;
|
|
strp->oldemul = p->p_emul;
|
|
strp->olduid = kauth_cred_getuid(pc);
|
|
strp->oldgid = kauth_cred_getgid(pc);
|
|
|
|
/* Elevate privileges as desired */
|
|
if (issuser) {
|
|
if (ISSET(strp->flags, STR_PROC_SETEUID)) {
|
|
strp->saveuid = systrace_seteuid(l, strp->seteuid);
|
|
SET(strp->flags, STR_PROC_DIDSETUGID);
|
|
}
|
|
if (ISSET(strp->flags, STR_PROC_SETEGID)) {
|
|
strp->savegid = systrace_setegid(l, strp->setegid);
|
|
SET(strp->flags, STR_PROC_DIDSETUGID);
|
|
}
|
|
} else
|
|
CLR(strp->flags,
|
|
STR_PROC_SETEUID|STR_PROC_SETEGID|STR_PROC_DIDSETUGID);
|
|
|
|
out:
|
|
systrace_unlock();
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
systrace_exit(struct lwp *l, register_t code, void *v, register_t retval[],
|
|
int error)
|
|
{
|
|
const struct sysent *callp;
|
|
struct str_process *strp;
|
|
struct fsystrace *fst;
|
|
struct proc *p = l->l_proc;
|
|
kauth_cred_t pc;
|
|
|
|
/* Report change in emulation */
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp == NULL || strp->oldemul == NULL) {
|
|
systrace_unlock();
|
|
return;
|
|
}
|
|
DPRINTF(("exit syscall %lu, oldemul %p\n", (u_long)code, strp->oldemul));
|
|
|
|
/* Return to old privileges */
|
|
pc = p->p_cred;
|
|
if (ISSET(strp->flags, STR_PROC_DIDSETUGID)) {
|
|
if (ISSET(strp->flags, STR_PROC_SETEUID)) {
|
|
if (kauth_cred_geteuid(pc) == strp->seteuid)
|
|
systrace_seteuid(l, strp->saveuid);
|
|
}
|
|
if (ISSET(strp->flags, STR_PROC_SETEGID)) {
|
|
if (kauth_cred_getegid(pc) == strp->setegid)
|
|
systrace_setegid(l, strp->savegid);
|
|
}
|
|
}
|
|
CLR(strp->flags,
|
|
STR_PROC_SETEUID|STR_PROC_SETEGID|STR_PROC_DIDSETUGID);
|
|
|
|
systrace_replacefree(strp);
|
|
|
|
if (p->p_flag & PK_SUGID) {
|
|
if ((fst = strp->parent) == NULL || !fst->issuser) {
|
|
systrace_unlock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* See if we should force a report */
|
|
if (ISSET(strp->flags, STR_PROC_REPORT)) {
|
|
CLR(strp->flags, STR_PROC_REPORT);
|
|
strp->oldemul = NULL;
|
|
}
|
|
|
|
if (p->p_emul != strp->oldemul && strp != NULL) {
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, p);
|
|
systrace_unlock();
|
|
|
|
/* Old policy is without meaning now */
|
|
if (strp->policy) {
|
|
systrace_closepolicy(fst, strp->policy);
|
|
strp->policy = NULL;
|
|
}
|
|
systrace_msg_emul(fst, strp);
|
|
} else
|
|
systrace_unlock();
|
|
|
|
/* Report if effective uid or gid changed */
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp != NULL && (strp->olduid != kauth_cred_getuid(p->p_cred) ||
|
|
strp->oldgid != kauth_cred_getgid(p->p_cred))) {
|
|
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, p);
|
|
systrace_unlock();
|
|
|
|
systrace_msg_ugid(fst, strp);
|
|
} else
|
|
systrace_unlock();
|
|
|
|
/* Report result from system call */
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp != NULL && ISSET(strp->flags, STR_PROC_SYSCALLRES)) {
|
|
size_t argsize;
|
|
|
|
CLR(strp->flags, STR_PROC_SYSCALLRES);
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, p);
|
|
systrace_unlock();
|
|
DPRINTF(("will ask syscall %lu, strp %p\n", (u_long)code, strp));
|
|
|
|
/* Get the (adjusted) argsize */
|
|
callp = p->p_emul->e_sysent + code;
|
|
argsize = callp->sy_argsize;
|
|
#ifdef _LP64
|
|
if (p->p_flag & PK_32)
|
|
argsize = argsize << 1;
|
|
#endif
|
|
|
|
systrace_msg_result(fst, strp, error, code, argsize, v, retval);
|
|
} else {
|
|
DPRINTF(("will not ask syscall %lu, strp %p\n", (u_long)code, strp));
|
|
systrace_unlock();
|
|
}
|
|
}
|
|
|
|
uid_t
|
|
systrace_seteuid(struct lwp *l, uid_t euid)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
kauth_cred_t cred, ncred;
|
|
uid_t oeuid;
|
|
|
|
ncred = kauth_cred_alloc();
|
|
proc_crmod_enter();
|
|
cred = p->p_cred;
|
|
|
|
oeuid = kauth_cred_geteuid(cred);
|
|
if (oeuid == euid) {
|
|
proc_crmod_leave(cred, ncred, false);
|
|
return (oeuid);
|
|
}
|
|
|
|
/* Copy credentials so other references do not see our changes. */
|
|
kauth_cred_clone(cred, ncred);
|
|
kauth_cred_seteuid(ncred, euid);
|
|
|
|
/* Broadcast our credentials to the process and other LWPs. */
|
|
proc_crmod_leave(ncred, cred, true);
|
|
|
|
return (oeuid);
|
|
}
|
|
|
|
gid_t
|
|
systrace_setegid(struct lwp *l, gid_t egid)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
kauth_cred_t cred, ncred;
|
|
gid_t oegid;
|
|
|
|
ncred = kauth_cred_alloc();
|
|
proc_crmod_enter();
|
|
cred = p->p_cred;
|
|
|
|
oegid = kauth_cred_getegid(cred);
|
|
if (oegid == egid) {
|
|
proc_crmod_leave(cred, ncred, false);
|
|
return (oegid);
|
|
}
|
|
|
|
/* Copy credentials so other references do not see our changes. */
|
|
kauth_cred_clone(cred, ncred);
|
|
kauth_cred_setegid(ncred, egid);
|
|
|
|
/* Broadcast our credentials to the process and other LWPs. */
|
|
proc_crmod_leave(cred, ncred, true);
|
|
|
|
return (oegid);
|
|
}
|
|
|
|
/* Called with fst locked */
|
|
|
|
int
|
|
systrace_answer(struct str_process *strp, struct systrace_answer *ans)
|
|
{
|
|
int error = 0;
|
|
|
|
DPRINTF(("%s: %u: policy %d\n", __func__,
|
|
ans->stra_pid, ans->stra_policy));
|
|
|
|
if (!POLICY_VALID(ans->stra_policy)) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Check if answer is in sync with us */
|
|
if (ans->stra_seqnr != strp->seqnr) {
|
|
error = ESRCH;
|
|
goto out;
|
|
}
|
|
|
|
if ((error = systrace_processready(strp)) != 0)
|
|
goto out;
|
|
|
|
strp->answer = ans->stra_policy;
|
|
strp->error = ans->stra_error;
|
|
if (!strp->error)
|
|
strp->error = EPERM;
|
|
if (ISSET(ans->stra_flags, SYSTR_FLAGS_RESULT))
|
|
SET(strp->flags, STR_PROC_SYSCALLRES);
|
|
|
|
/* See if we should elevate privileges for this system call */
|
|
if (ISSET(ans->stra_flags, SYSTR_FLAGS_SETEUID)) {
|
|
SET(strp->flags, STR_PROC_SETEUID);
|
|
strp->seteuid = ans->stra_seteuid;
|
|
}
|
|
if (ISSET(ans->stra_flags, SYSTR_FLAGS_SETEGID)) {
|
|
SET(strp->flags, STR_PROC_SETEGID);
|
|
strp->setegid = ans->stra_setegid;
|
|
}
|
|
|
|
|
|
/* Clearing the flag indicates to the process that it woke up */
|
|
CLR(strp->flags, STR_PROC_WAITANSWER);
|
|
wakeup(strp);
|
|
out:
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
systrace_setscriptname(struct str_process *strp,
|
|
struct systrace_scriptname *ans)
|
|
{
|
|
strlcpy(strp->scriptname, ans->sn_scriptname,
|
|
sizeof(strp->scriptname));
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_policy(struct fsystrace *fst, struct systrace_policy *pol)
|
|
{
|
|
struct str_policy *strpol;
|
|
struct str_process *strp;
|
|
|
|
switch(pol->strp_op) {
|
|
case SYSTR_POLICY_NEW:
|
|
DPRINTF(("%s: new, ents %d\n", __func__,
|
|
pol->strp_maxents));
|
|
if (pol->strp_maxents <= 0 || pol->strp_maxents > 1024)
|
|
return (EINVAL);
|
|
strpol = systrace_newpolicy(fst, pol->strp_maxents);
|
|
if (strpol == NULL)
|
|
return (ENOBUFS);
|
|
pol->strp_num = strpol->nr;
|
|
break;
|
|
case SYSTR_POLICY_ASSIGN:
|
|
DPRINTF(("%s: %d -> pid %d\n", __func__,
|
|
pol->strp_num, pol->strp_pid));
|
|
|
|
/* Find right policy by number */
|
|
TAILQ_FOREACH(strpol, &fst->policies, next)
|
|
if (strpol->nr == pol->strp_num)
|
|
break;
|
|
if (strpol == NULL)
|
|
return (EINVAL);
|
|
|
|
strp = systrace_findpid(fst, pol->strp_pid);
|
|
if (strp == NULL)
|
|
return (EINVAL);
|
|
|
|
/* Check that emulation matches */
|
|
if (strpol->emul && strpol->emul != strp->proc->p_emul) {
|
|
struct proc *p = strp->proc;
|
|
mutex_enter(&p->p_mutex);
|
|
proc_delref(p);
|
|
mutex_exit(&p->p_mutex);
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (strp->policy)
|
|
systrace_closepolicy(fst, strp->policy);
|
|
strp->policy = strpol;
|
|
strpol->refcount++;
|
|
|
|
/* Record emulation for this policy */
|
|
if (strpol->emul == NULL)
|
|
strpol->emul = strp->proc->p_emul;
|
|
|
|
mutex_enter(&strp->proc->p_mutex);
|
|
proc_delref(strp->proc);
|
|
mutex_exit(&strp->proc->p_mutex);
|
|
break;
|
|
case SYSTR_POLICY_MODIFY:
|
|
DPRINTF(("%s: %d: code %d -> policy %d\n", __func__,
|
|
pol->strp_num, pol->strp_code, pol->strp_policy));
|
|
if (!POLICY_VALID(pol->strp_policy))
|
|
return (EINVAL);
|
|
TAILQ_FOREACH(strpol, &fst->policies, next)
|
|
if (strpol->nr == pol->strp_num)
|
|
break;
|
|
if (strpol == NULL)
|
|
return (EINVAL);
|
|
if (pol->strp_code < 0 || pol->strp_code >= strpol->nsysent)
|
|
return (EINVAL);
|
|
strpol->sysent[pol->strp_code] = pol->strp_policy;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_processready(struct str_process *strp)
|
|
{
|
|
if (ISSET(strp->flags, STR_PROC_ONQUEUE))
|
|
return (EBUSY);
|
|
|
|
if (!ISSET(strp->flags, STR_PROC_WAITANSWER))
|
|
return (EBUSY);
|
|
|
|
/* XXX - ignore until systrace knows about lwps. :-(
|
|
if (strp->proc->p_stat != LSSLEEP)
|
|
return (EBUSY);
|
|
*/
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_getcwd(struct fsystrace *fst, struct str_process *strp)
|
|
{
|
|
#ifdef __NetBSD__
|
|
struct cwdinfo *mycwdp, *cwdp;
|
|
#else
|
|
struct filedesc *myfdp, *fdp;
|
|
#endif
|
|
int error;
|
|
|
|
DPRINTF(("%s: %d\n", __func__, strp->pid));
|
|
|
|
error = systrace_processready(strp);
|
|
if (error)
|
|
return (error);
|
|
|
|
#ifdef __NetBSD__
|
|
mycwdp = curproc->p_cwdi;
|
|
cwdp = strp->proc->p_cwdi;
|
|
if (mycwdp == NULL || cwdp == NULL)
|
|
return (EINVAL);
|
|
|
|
/* Store our current values */
|
|
fst->fd_pid = strp->pid;
|
|
rw_enter(&mycwdp->cwdi_lock, RW_READER);
|
|
fst->fd_cdir = mycwdp->cwdi_cdir;
|
|
fst->fd_rdir = mycwdp->cwdi_rdir;
|
|
if ((mycwdp->cwdi_cdir = cwdp->cwdi_cdir) != NULL)
|
|
VREF(mycwdp->cwdi_cdir);
|
|
if ((mycwdp->cwdi_rdir = cwdp->cwdi_rdir) != NULL)
|
|
VREF(mycwdp->cwdi_rdir);
|
|
rw_exit(&mycwdp->cwdi_lock);
|
|
#else
|
|
myfdp = curlwp->p_fd;
|
|
fdp = strp->proc->p_fd;
|
|
if (myfdp == NULL || fdp == NULL)
|
|
return (EINVAL);
|
|
|
|
/* Store our current values */
|
|
fst->fd_pid = strp->pid;
|
|
fst->fd_cdir = myfdp->fd_cdir;
|
|
fst->fd_rdir = myfdp->fd_rdir;
|
|
|
|
if ((myfdp->fd_cdir = fdp->fd_cdir) != NULL)
|
|
VREF(myfdp->fd_cdir);
|
|
if ((myfdp->fd_rdir = fdp->fd_rdir) != NULL)
|
|
VREF(myfdp->fd_rdir);
|
|
#endif
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_io(struct str_process *strp, struct systrace_io *io)
|
|
{
|
|
struct proc *t = strp->proc;
|
|
struct lwp *l = curlwp;
|
|
struct uio uio;
|
|
struct iovec iov;
|
|
int error = 0;
|
|
|
|
DPRINTF(("%s: %u: %p(%lu)\n", __func__,
|
|
io->strio_pid, io->strio_offs, (u_long)io->strio_len));
|
|
|
|
switch (io->strio_op) {
|
|
case SYSTR_READ:
|
|
uio.uio_rw = UIO_READ;
|
|
break;
|
|
case SYSTR_WRITE:
|
|
uio.uio_rw = UIO_WRITE;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
error = systrace_processready(strp);
|
|
if (error)
|
|
goto out;
|
|
|
|
iov.iov_base = io->strio_addr;
|
|
iov.iov_len = io->strio_len;
|
|
uio.uio_iov = &iov;
|
|
uio.uio_iovcnt = 1;
|
|
uio.uio_offset = (off_t)(unsigned long)io->strio_offs;
|
|
uio.uio_resid = io->strio_len;
|
|
uio.uio_vmspace = l->l_proc->p_vmspace;
|
|
|
|
error = kauth_authorize_process(l->l_cred, KAUTH_PROCESS_CANSYSTRACE,
|
|
t, NULL, NULL, NULL);
|
|
if (error)
|
|
return (error);
|
|
|
|
#ifdef __NetBSD__
|
|
{
|
|
struct lwp *tl;
|
|
mutex_enter(&t->p_smutex);
|
|
tl = proc_representative_lwp(t, NULL, 1);
|
|
lwp_addref(tl);
|
|
mutex_exit(&t->p_smutex);
|
|
error = process_domem(l, tl, &uio);
|
|
lwp_delref(tl);
|
|
}
|
|
#else
|
|
error = procfs_domem(p, t, NULL, &uio);
|
|
#endif
|
|
io->strio_len -= uio.uio_resid;
|
|
out:
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
systrace_attach(struct fsystrace *fst, pid_t pid)
|
|
{
|
|
int error = 0;
|
|
struct proc *proc, *p = curproc;
|
|
|
|
mutex_enter(&proclist_lock);
|
|
|
|
if ((proc = p_find(pid, PFIND_LOCKED)) == NULL) {
|
|
mutex_exit(&proclist_lock);
|
|
return (ESRCH);
|
|
}
|
|
|
|
mutex_enter(&proc->p_mutex);
|
|
error = proc_addref(proc);
|
|
mutex_exit(&proclist_lock);
|
|
|
|
if (error != 0) {
|
|
mutex_exit(&proc->p_mutex);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* You can't attach to a process if:
|
|
* (1) it's the process that's doing the attaching,
|
|
*/
|
|
if (proc->p_pid == p->p_pid) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* (2) it's a system process
|
|
*/
|
|
if (ISSET(proc->p_flag, PK_SYSTEM)) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* (3) it's being traced already
|
|
*/
|
|
if (ISSET(proc->p_flag, PK_SYSTRACE)) {
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* (4) the security model prevents it it.
|
|
*/
|
|
error = kauth_authorize_process(kauth_cred_get(),
|
|
KAUTH_PROCESS_CANSYSTRACE, proc, NULL, NULL, NULL);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = systrace_insert_process(fst, proc, NULL);
|
|
|
|
#if defined(__NetBSD__) && defined(__HAVE_SYSCALL_INTERN)
|
|
/*
|
|
* Make sure we're using the version of the syscall handler that
|
|
* has systrace hooks.
|
|
*/
|
|
if (!error)
|
|
(*proc->p_emul->e_syscall_intern)(proc);
|
|
#endif
|
|
out:
|
|
proc_delref(proc);
|
|
mutex_exit(&proc->p_mutex);
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
systrace_execve0(struct proc *p)
|
|
{
|
|
struct str_process *strp;
|
|
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
strp->isscript = 0;
|
|
systrace_unlock();
|
|
}
|
|
|
|
void
|
|
systrace_execve1(char *path, struct proc *p)
|
|
{
|
|
struct str_process *strp;
|
|
struct fsystrace *fst;
|
|
struct str_message msg;
|
|
struct str_msg_execve *msg_execve;
|
|
|
|
do {
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp == NULL) {
|
|
systrace_unlock();
|
|
return;
|
|
}
|
|
|
|
msg_execve = &msg.msg_data.msg_execve;
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
/*
|
|
* susers will get the execve call anyway. Also, if
|
|
* we're not allowed to control the process, escape.
|
|
*/
|
|
if (fst->issuser ||
|
|
fst->p_ruid != kauth_cred_getuid(p->p_cred) ||
|
|
fst->p_rgid != kauth_cred_getgid(p->p_cred)) {
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
return;
|
|
}
|
|
|
|
strlcpy(msg_execve->path, path, sizeof(msg_execve->path));
|
|
} while (systrace_make_msg(strp, SYSTR_MSG_EXECVE, &msg) != 0);
|
|
}
|
|
|
|
/* Prepare to replace arguments */
|
|
|
|
int
|
|
systrace_preprepl(struct str_process *strp, struct systrace_replace *repl)
|
|
{
|
|
size_t len;
|
|
int i, ret = 0;
|
|
|
|
ret = systrace_processready(strp);
|
|
if (ret)
|
|
return (ret);
|
|
|
|
systrace_replacefree(strp);
|
|
|
|
if (repl->strr_nrepl < 0 || repl->strr_nrepl > SYSTR_MAXARGS)
|
|
return (EINVAL);
|
|
|
|
for (i = 0, len = 0; i < repl->strr_nrepl; i++) {
|
|
if (repl->strr_argind[i] < 0 ||
|
|
repl->strr_argind[i] >= SYSTR_MAXARGS)
|
|
return (EINVAL);
|
|
if (repl->strr_offlen[i] == 0)
|
|
continue;
|
|
len += repl->strr_offlen[i];
|
|
if (repl->strr_offlen[i] > SYSTR_MAXREPLEN ||
|
|
repl->strr_off[i] > SYSTR_MAXREPLEN ||
|
|
len > SYSTR_MAXREPLEN)
|
|
return (EINVAL);
|
|
if (repl->strr_offlen[i] + repl->strr_off[i] > len)
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Make sure that the length adds up */
|
|
if (repl->strr_len != len)
|
|
return (EINVAL);
|
|
|
|
/* Check against a maximum length */
|
|
if (repl->strr_len > SYSTR_MAXREPLEN)
|
|
return (EINVAL);
|
|
|
|
strp->replace = (struct systrace_replace *)
|
|
malloc(sizeof(struct systrace_replace) + len, M_XDATA, M_WAITOK);
|
|
|
|
memcpy(strp->replace, repl, sizeof(struct systrace_replace));
|
|
ret = copyin(repl->strr_base, strp->replace + 1, len);
|
|
if (ret) {
|
|
free(strp->replace, M_XDATA);
|
|
strp->replace = NULL;
|
|
return (ret);
|
|
}
|
|
|
|
/* Adjust the offset */
|
|
repl = strp->replace;
|
|
repl->strr_base = (void *)(repl + 1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Replace the arguments with arguments from the monitoring process.
|
|
*/
|
|
|
|
int
|
|
systrace_replace(struct str_process *strp, size_t argsize, register_t args[])
|
|
{
|
|
struct proc *p = strp->proc;
|
|
struct systrace_replace *repl = strp->replace;
|
|
void *sg, *kdata, *udata, *kbase, *ubase;
|
|
int i, maxarg, ind, ret = 0;
|
|
|
|
maxarg = argsize/sizeof(register_t);
|
|
#ifdef __NetBSD__
|
|
sg = stackgap_init(p, 0);
|
|
ubase = stackgap_alloc(p, &sg, repl->strr_len);
|
|
#else
|
|
sg = stackgap_init(p->p_emul);
|
|
ubase = stackgap_alloc(&sg, repl->strr_len);
|
|
#endif
|
|
if (ubase == NULL) {
|
|
ret = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
kbase = repl->strr_base;
|
|
for (i = 0; i < maxarg && i < repl->strr_nrepl; i++) {
|
|
ind = repl->strr_argind[i];
|
|
if (ind < 0 || ind >= maxarg) {
|
|
ret = EINVAL;
|
|
goto out;
|
|
}
|
|
if (repl->strr_offlen[i] == 0) {
|
|
args[ind] = repl->strr_off[i];
|
|
continue;
|
|
}
|
|
kdata = (char *)kbase + repl->strr_off[i];
|
|
udata = (char *)ubase + repl->strr_off[i];
|
|
if (repl->strr_flags[i] & SYSTR_NOLINKS) {
|
|
ret = systrace_fname(strp, kdata, repl->strr_offlen[i]);
|
|
if (ret != 0)
|
|
goto out;
|
|
}
|
|
if (copyout(kdata, udata, repl->strr_offlen[i])) {
|
|
ret = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Replace the argument with the new address */
|
|
args[ind] = (register_t)(intptr_t)udata;
|
|
}
|
|
|
|
out:
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
systrace_fname(struct str_process *strp, void *kdata, size_t len)
|
|
{
|
|
if (strp->nfname >= SYSTR_MAXFNAME || len < 2)
|
|
return EINVAL;
|
|
|
|
strp->fname[strp->nfname] = kdata;
|
|
strp->fname[strp->nfname][len - 1] = '\0';
|
|
strp->nfname++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
systrace_replacefree(struct str_process *strp)
|
|
{
|
|
|
|
if (strp->replace != NULL) {
|
|
free(strp->replace, M_XDATA);
|
|
strp->replace = NULL;
|
|
}
|
|
while (strp->nfname > 0) {
|
|
strp->nfname--;
|
|
strp->fname[strp->nfname] = NULL;
|
|
}
|
|
}
|
|
|
|
int
|
|
systrace_scriptname(struct proc *p, char *dst)
|
|
{
|
|
struct str_process *strp;
|
|
struct fsystrace *fst;
|
|
int error = 0;
|
|
|
|
systrace_lock();
|
|
strp = p->p_systrace;
|
|
if (strp == NULL) {
|
|
systrace_unlock();
|
|
return (EINVAL);
|
|
}
|
|
|
|
fst = strp->parent;
|
|
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
if (!fst->issuser && (ISSET(p->p_flag, PK_SUGID) ||
|
|
fst->p_ruid != kauth_cred_getuid(p->p_cred) ||
|
|
fst->p_rgid != kauth_cred_getgid(p->p_cred))) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
if (strp->scriptname[0] == '\0') {
|
|
error = ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy(dst, strp->scriptname, MAXPATHLEN);
|
|
strp->isscript = 1;
|
|
|
|
out:
|
|
strp->scriptname[0] = '\0';
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
systrace_namei(struct nameidata *ndp)
|
|
{
|
|
struct str_process *strp;
|
|
struct fsystrace *fst = NULL; /* XXXGCC */
|
|
struct componentname *cnp = &ndp->ni_cnd;
|
|
size_t i;
|
|
int hamper = 0;
|
|
|
|
systrace_lock();
|
|
strp = cnp->cn_lwp->l_proc->p_systrace;
|
|
if (strp != NULL) {
|
|
fst = strp->parent;
|
|
SYSTRACE_LOCK(fst, curlwp);
|
|
systrace_unlock();
|
|
|
|
for (i = 0; i < strp->nfname; i++) {
|
|
if (strcmp(cnp->cn_pnbuf, strp->fname[i]) == 0) {
|
|
hamper = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hamper && strp->isscript &&
|
|
strcmp(cnp->cn_pnbuf, strp->scriptname) == 0)
|
|
hamper = 1;
|
|
|
|
SYSTRACE_UNLOCK(fst, curlwp);
|
|
} else
|
|
systrace_unlock();
|
|
|
|
if (hamper) {
|
|
/* ELOOP if namei() tries to readlink */
|
|
ndp->ni_loopcnt = MAXSYMLINKS;
|
|
cnp->cn_flags &= ~FOLLOW;
|
|
cnp->cn_flags |= NOFOLLOW;
|
|
}
|
|
}
|
|
|
|
struct str_process *
|
|
systrace_findpid(struct fsystrace *fst, pid_t pid)
|
|
{
|
|
struct str_process *strp;
|
|
struct proc *proc = NULL;
|
|
|
|
TAILQ_FOREACH(strp, &fst->processes, next)
|
|
if (strp->pid == pid)
|
|
break;
|
|
|
|
if (strp == NULL)
|
|
return (NULL);
|
|
|
|
proc = systrace_find(strp);
|
|
|
|
return (proc ? strp : NULL);
|
|
}
|
|
|
|
int
|
|
systrace_detach(struct str_process *strp)
|
|
{
|
|
struct proc *proc;
|
|
struct fsystrace *fst = NULL;
|
|
int error = 0;
|
|
|
|
DPRINTF(("%s: Trying to detach from %d\n", __func__, strp->pid));
|
|
|
|
if ((proc = systrace_find(strp)) != NULL) {
|
|
CLR(proc->p_flag, PK_SYSTRACE);
|
|
proc->p_systrace = NULL;
|
|
} else
|
|
error = ESRCH;
|
|
|
|
if (ISSET(strp->flags, STR_PROC_WAITANSWER)) {
|
|
CLR(strp->flags, STR_PROC_WAITANSWER);
|
|
wakeup(strp);
|
|
}
|
|
|
|
fst = strp->parent;
|
|
systrace_wakeup(fst);
|
|
|
|
TAILQ_REMOVE(&fst->processes, strp, next);
|
|
fst->nprocesses--;
|
|
|
|
if (strp->policy)
|
|
systrace_closepolicy(fst, strp->policy);
|
|
systrace_replacefree(strp);
|
|
pool_put(&systr_proc_pl, strp);
|
|
|
|
mutex_enter(&strp->proc->p_mutex);
|
|
proc_delref(strp->proc);
|
|
mutex_exit(&strp->proc->p_mutex);
|
|
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
systrace_closepolicy(struct fsystrace *fst, struct str_policy *policy)
|
|
{
|
|
if (--policy->refcount)
|
|
return;
|
|
|
|
fst->npolicies--;
|
|
|
|
if (policy->nsysent)
|
|
free(policy->sysent, M_XDATA);
|
|
|
|
TAILQ_REMOVE(&fst->policies, policy, next);
|
|
|
|
pool_put(&systr_policy_pl, policy);
|
|
}
|
|
|
|
|
|
int
|
|
systrace_insert_process(struct fsystrace *fst, struct proc *proc,
|
|
struct str_process **pstrp)
|
|
{
|
|
struct str_process *strp;
|
|
|
|
strp = pool_get(&systr_proc_pl, PR_NOWAIT);
|
|
if (strp == NULL)
|
|
return (ENOBUFS);
|
|
|
|
memset((void *)strp, 0, sizeof(struct str_process));
|
|
strp->pid = proc->p_pid;
|
|
strp->proc = proc;
|
|
strp->parent = fst;
|
|
|
|
TAILQ_INSERT_TAIL(&fst->processes, strp, next);
|
|
fst->nprocesses++;
|
|
|
|
proc->p_systrace = strp;
|
|
SET(proc->p_flag, PK_SYSTRACE);
|
|
|
|
/* Pass the new pointer back to the caller */
|
|
if (pstrp != NULL)
|
|
*pstrp = strp;
|
|
|
|
return (0);
|
|
}
|
|
|
|
struct str_policy *
|
|
systrace_newpolicy(struct fsystrace *fst, int maxents)
|
|
{
|
|
struct str_policy *pol;
|
|
int i;
|
|
|
|
if (fst->npolicies > SYSTR_MAX_POLICIES && !fst->issuser) {
|
|
struct str_policy *tmp;
|
|
|
|
/* Try to find a policy for freeing */
|
|
TAILQ_FOREACH(tmp, &fst->policies, next) {
|
|
if (tmp->refcount == 1)
|
|
break;
|
|
}
|
|
|
|
if (tmp == NULL)
|
|
return (NULL);
|
|
|
|
/* Notify userland about freed policy */
|
|
systrace_msg_policyfree(fst, tmp);
|
|
/* Free this policy */
|
|
systrace_closepolicy(fst, tmp);
|
|
}
|
|
|
|
pol = pool_get(&systr_policy_pl, PR_NOWAIT);
|
|
if (pol == NULL)
|
|
return (NULL);
|
|
|
|
DPRINTF(("%s: allocating %d -> %lu\n", __func__,
|
|
maxents, (u_long)maxents * sizeof(int)));
|
|
|
|
memset((void *)pol, 0, sizeof(struct str_policy));
|
|
|
|
pol->sysent = (u_char *)malloc(maxents * sizeof(u_char),
|
|
M_XDATA, M_WAITOK);
|
|
pol->nsysent = maxents;
|
|
for (i = 0; i < maxents; i++)
|
|
pol->sysent[i] = SYSTR_POLICY_ASK;
|
|
|
|
fst->npolicies++;
|
|
pol->nr = fst->npolicynr++;
|
|
pol->refcount = 1;
|
|
|
|
TAILQ_INSERT_TAIL(&fst->policies, pol, next);
|
|
|
|
return (pol);
|
|
}
|
|
|
|
int
|
|
systrace_msg_ask(struct fsystrace *fst, struct str_process *strp, int code,
|
|
size_t argsize, register_t args[])
|
|
{
|
|
struct str_message msg;
|
|
struct str_msg_ask *msg_ask = &msg.msg_data.msg_ask;
|
|
int i;
|
|
|
|
msg_ask->code = code;
|
|
msg_ask->argsize = argsize;
|
|
for (i = 0; i < (argsize/sizeof(register_t)) && i < SYSTR_MAXARGS; i++)
|
|
msg_ask->args[i] = args[i];
|
|
|
|
return (systrace_make_msg(strp, SYSTR_MSG_ASK, &msg));
|
|
}
|
|
|
|
int
|
|
systrace_msg_result(struct fsystrace *fst, struct str_process *strp, int error,
|
|
int code, size_t argsize, register_t args[], register_t rval[])
|
|
{
|
|
struct str_message msg;
|
|
struct str_msg_ask *msg_ask = &msg.msg_data.msg_ask;
|
|
int i;
|
|
|
|
msg_ask->code = code;
|
|
msg_ask->argsize = argsize;
|
|
msg_ask->result = error;
|
|
for (i = 0; i < (argsize/sizeof(register_t)) && i < SYSTR_MAXARGS; i++)
|
|
msg_ask->args[i] = args[i];
|
|
|
|
msg_ask->rval[0] = rval[0];
|
|
msg_ask->rval[1] = rval[1];
|
|
|
|
return (systrace_make_msg(strp, SYSTR_MSG_RES, &msg));
|
|
}
|
|
|
|
int
|
|
systrace_msg_emul(struct fsystrace *fst, struct str_process *strp)
|
|
{
|
|
struct str_message msg;
|
|
struct str_msg_emul *msg_emul = &msg.msg_data.msg_emul;
|
|
struct proc *p = strp->proc;
|
|
|
|
memcpy(msg_emul->emul, p->p_emul->e_name, SYSTR_EMULEN);
|
|
|
|
return (systrace_make_msg(strp, SYSTR_MSG_EMUL, &msg));
|
|
}
|
|
|
|
int
|
|
systrace_msg_ugid(struct fsystrace *fst, struct str_process *strp)
|
|
{
|
|
struct str_message msg;
|
|
struct str_msg_ugid *msg_ugid = &msg.msg_data.msg_ugid;
|
|
struct proc *p = strp->proc;
|
|
|
|
msg_ugid->uid = kauth_cred_getuid(p->p_cred);
|
|
msg_ugid->gid = kauth_cred_getgid(p->p_cred);
|
|
|
|
return (systrace_make_msg(strp, SYSTR_MSG_UGID, &msg));
|
|
}
|
|
|
|
int
|
|
systrace_make_msg(struct str_process *strp, int type, struct str_message *tmsg)
|
|
{
|
|
struct str_msgcontainer *cont;
|
|
struct str_message *msg;
|
|
struct fsystrace *fst = strp->parent;
|
|
int st;
|
|
|
|
cont = pool_get(&systr_msgcontainer_pl, PR_WAITOK);
|
|
memset(cont, 0, sizeof(struct str_msgcontainer));
|
|
cont->strp = strp;
|
|
|
|
msg = &cont->msg;
|
|
|
|
/* Copy the already filled in fields */
|
|
memcpy(&msg->msg_data, &tmsg->msg_data, sizeof(msg->msg_data));
|
|
|
|
/* Add the extra fields to the message */
|
|
msg->msg_seqnr = ++strp->seqnr;
|
|
msg->msg_type = type;
|
|
msg->msg_pid = strp->pid;
|
|
if (strp->policy)
|
|
msg->msg_policy = strp->policy->nr;
|
|
else
|
|
msg->msg_policy = -1;
|
|
|
|
SET(strp->flags, STR_PROC_WAITANSWER);
|
|
if (ISSET(strp->flags, STR_PROC_ONQUEUE))
|
|
goto out;
|
|
|
|
TAILQ_INSERT_TAIL(&fst->messages, cont, next);
|
|
SET(strp->flags, STR_PROC_ONQUEUE);
|
|
|
|
out:
|
|
systrace_wakeup(fst);
|
|
|
|
/* Release the lock - XXX */
|
|
SYSTRACE_UNLOCK(fst, strp->proc);
|
|
|
|
while (1) {
|
|
st = tsleep(strp, PWAIT, "systrmsg", 0);
|
|
if (st != 0)
|
|
return (ERESTART);
|
|
/* If we detach, then everything is permitted */
|
|
if ((strp = curproc->p_systrace) == NULL)
|
|
return (0);
|
|
if (!ISSET(strp->flags, STR_PROC_WAITANSWER))
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_msg_child(struct fsystrace *fst, struct str_process *strp, pid_t npid)
|
|
{
|
|
struct str_msgcontainer *cont;
|
|
struct str_message *msg;
|
|
struct str_msg_child *msg_child;
|
|
|
|
cont = pool_get(&systr_msgcontainer_pl, PR_WAITOK);
|
|
memset(cont, 0, sizeof(struct str_msgcontainer));
|
|
cont->strp = strp;
|
|
|
|
msg = &cont->msg;
|
|
|
|
DPRINTF(("%s: %p: pid %d -> pid %d\n", __func__,
|
|
msg, strp->pid, npid));
|
|
|
|
msg_child = &msg->msg_data.msg_child;
|
|
|
|
msg->msg_type = SYSTR_MSG_CHILD;
|
|
msg->msg_pid = strp->pid;
|
|
if (strp->policy)
|
|
msg->msg_policy = strp->policy->nr;
|
|
else
|
|
msg->msg_policy = -1;
|
|
msg_child->new_pid = npid;
|
|
|
|
TAILQ_INSERT_TAIL(&fst->messages, cont, next);
|
|
|
|
systrace_wakeup(fst);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
systrace_msg_policyfree(struct fsystrace *fst, struct str_policy *strpol)
|
|
{
|
|
struct str_msgcontainer *cont;
|
|
struct str_message *msg;
|
|
|
|
cont = pool_get(&systr_msgcontainer_pl, PR_WAITOK);
|
|
memset(cont, 0, sizeof(struct str_msgcontainer));
|
|
|
|
msg = &cont->msg;
|
|
|
|
DPRINTF(("%s: free %d\n", __func__, strpol->nr));
|
|
|
|
msg->msg_type = SYSTR_MSG_POLICYFREE;
|
|
msg->msg_policy = strpol->nr;
|
|
|
|
TAILQ_INSERT_TAIL(&fst->messages, cont, next);
|
|
|
|
systrace_wakeup(fst);
|
|
|
|
return (0);
|
|
}
|