998afe8b1c
release the lock and exit. It could be argued that we only ever call the function with strp being valid so the test isn't needed, but that requires the caller not to change, or be altered/broken. Fixes Coverity CID 2357, strp deferenced before NULL check.
1853 lines
41 KiB
C
1853 lines
41 KiB
C
/* $NetBSD: kern_systrace.c,v 1.52 2006/03/18 17:44:13 chris 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.52 2006/03/18 17:44:13 chris 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/lock.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/sa.h>
|
|
#include <sys/savar.h>
|
|
|
|
#include <compat/common/compat_util.h>
|
|
|
|
#ifdef __NetBSD__
|
|
#define SYSTRACE_LOCK(fst, p) lockmgr(&fst->lock, LK_EXCLUSIVE, NULL)
|
|
#define SYSTRACE_UNLOCK(fst, p) lockmgr(&fst->lock, LK_RELEASE, NULL)
|
|
#else
|
|
#define SYSTRACE_LOCK(fst, p) lockmgr(&fst->lock, LK_EXCLUSIVE, NULL, p)
|
|
#define SYSTRACE_UNLOCK(fst, p) lockmgr(&fst->lock, LK_RELEASE, NULL, p)
|
|
#endif
|
|
#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 *, struct ucred *,
|
|
int);
|
|
int systracef_write(struct file *, off_t *, struct uio *, struct ucred *,
|
|
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 *, struct ucred *);
|
|
int systracef_write(struct file *, off_t *, struct uio *, struct ucred *);
|
|
int systracef_select(struct file *, int, struct proc *);
|
|
int systracef_ioctl(struct file *, u_long, caddr_t, 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 proc *, uid_t);
|
|
gid_t systrace_setegid(struct proc *, 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 *, caddr_t, 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);
|
|
POOL_INIT(systr_policy_pl, sizeof(struct str_policy), 0, 0, 0, "strpolpl",
|
|
NULL);
|
|
POOL_INIT(systr_msgcontainer_pl, sizeof(struct str_msgcontainer), 0, 0, 0,
|
|
"strmsgpl", NULL);
|
|
|
|
struct lock systrace_lck = LOCK_INITIALIZER(PLOCK, "systrace", 0, 0);
|
|
#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,
|
|
};
|
|
#endif
|
|
|
|
#define DPRINTF(y) if (systrace_debug) printf y;
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
systracef_read(struct file *fp, off_t *poff, struct uio *uio,
|
|
struct ucred *cred
|
|
#ifdef __NetBSD__
|
|
, int flags
|
|
#endif
|
|
)
|
|
{
|
|
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((caddr_t)&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,
|
|
struct ucred *cred
|
|
#ifdef __NetBSD__
|
|
, int flags
|
|
#endif
|
|
)
|
|
{
|
|
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 = (struct fsystrace *)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:
|
|
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 STRIOCGETCWD:
|
|
pid = *(pid_t *)data;
|
|
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;
|
|
}
|
|
|
|
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);
|
|
psignal(q, SIGKILL);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
FREE(fp->f_data, M_XDATA);
|
|
fp->f_data = NULL;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
systrace_lock(void)
|
|
{
|
|
#ifdef __NetBSD__
|
|
lockmgr(&systrace_lck, LK_EXCLUSIVE, NULL);
|
|
#else
|
|
lockmgr(&systrace_lck, LK_EXCLUSIVE, NULL, curlwp);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
systrace_unlock(void)
|
|
{
|
|
#ifdef __NetBSD__
|
|
lockmgr(&systrace_lck, LK_RELEASE, NULL);
|
|
#else
|
|
lockmgr(&systrace_lck, LK_RELEASE, NULL, curlwp);
|
|
#endif
|
|
}
|
|
|
|
#ifndef __NetBSD__
|
|
void
|
|
systrace_init(void)
|
|
{
|
|
|
|
pool_init(&systr_proc_pl, sizeof(struct str_process), 0, 0, 0,
|
|
"strprocpl", NULL);
|
|
pool_init(&systr_policy_pl, sizeof(struct str_policy), 0, 0, 0,
|
|
"strpolpl", NULL);
|
|
pool_init(&systr_msgcontainer_pl, sizeof(struct str_msgcontainer),
|
|
0, 0, 0, "strmsgpl", NULL);
|
|
|
|
lockinit(&systrace_lck, PLOCK, "systrace", 0, 0);
|
|
}
|
|
#endif /* ! __NetBSD__ */
|
|
|
|
int
|
|
systraceopen(dev_t dev, int flag, int mode, struct lwp *l)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
struct fsystrace *fst;
|
|
struct file *fp;
|
|
int error, fd;
|
|
|
|
/* falloc() will use the descriptor for us. */
|
|
if ((error = falloc(l->l_proc, &fp, &fd)) != 0)
|
|
return (error);
|
|
|
|
MALLOC(fst, struct fsystrace *, sizeof(*fst), M_XDATA, M_WAITOK);
|
|
|
|
memset(fst, 0, sizeof(struct fsystrace));
|
|
lockinit(&fst->lock, PLOCK, "systrace", 0, 0);
|
|
|
|
TAILQ_INIT(&fst->processes);
|
|
TAILQ_INIT(&fst->messages);
|
|
TAILQ_INIT(&fst->policies);
|
|
|
|
if (suser(p->p_ucred, &p->p_acflag) == 0)
|
|
fst->issuser = 1;
|
|
fst->p_ruid = p->p_cred->p_ruid;
|
|
fst->p_rgid = p->p_cred->p_rgid;
|
|
|
|
return fdclone(l, fp, fd, flag, &systracefops, fst);
|
|
}
|
|
|
|
void
|
|
systrace_wakeup(struct fsystrace *fst)
|
|
{
|
|
wakeup((caddr_t)fst);
|
|
selwakeup(&fst->si);
|
|
}
|
|
|
|
struct proc *
|
|
systrace_find(struct str_process *strp)
|
|
{
|
|
struct proc *proc;
|
|
|
|
if ((proc = pfind(strp->pid)) == NULL)
|
|
return (NULL);
|
|
|
|
if (proc != strp->proc)
|
|
return (NULL);
|
|
|
|
if (!ISSET(proc->p_flag, P_SYSTRACE))
|
|
return (NULL);
|
|
|
|
return (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, P_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 */
|
|
psignal(p, SIGKILL);
|
|
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 proc *p, register_t code, void *v)
|
|
{
|
|
const struct sysent *callp;
|
|
struct str_process *strp;
|
|
struct str_policy *strpolicy;
|
|
struct fsystrace *fst;
|
|
struct pcred *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 & P_SUGID)) {
|
|
maycontrol = fst->p_ruid == p->p_cred->p_ruid &&
|
|
fst->p_rgid == p->p_cred->p_rgid;
|
|
}
|
|
|
|
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 & P_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 = pc->p_ruid;
|
|
strp->oldgid = pc->p_rgid;
|
|
|
|
/* Elevate privileges as desired */
|
|
if (issuser) {
|
|
if (ISSET(strp->flags, STR_PROC_SETEUID)) {
|
|
strp->saveuid = systrace_seteuid(p, strp->seteuid);
|
|
SET(strp->flags, STR_PROC_DIDSETUGID);
|
|
}
|
|
if (ISSET(strp->flags, STR_PROC_SETEGID)) {
|
|
strp->savegid = systrace_setegid(p, 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 proc *p, register_t code, void *v, register_t retval[],
|
|
int error)
|
|
{
|
|
const struct sysent *callp;
|
|
struct str_process *strp;
|
|
struct fsystrace *fst;
|
|
struct pcred *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 (pc->pc_ucred->cr_uid == strp->seteuid)
|
|
systrace_seteuid(p, strp->saveuid);
|
|
}
|
|
if (ISSET(strp->flags, STR_PROC_SETEGID)) {
|
|
if (pc->pc_ucred->cr_gid == strp->setegid)
|
|
systrace_setegid(p, strp->savegid);
|
|
}
|
|
}
|
|
CLR(strp->flags,
|
|
STR_PROC_SETEUID|STR_PROC_SETEGID|STR_PROC_DIDSETUGID);
|
|
|
|
systrace_replacefree(strp);
|
|
|
|
if (p->p_flag & P_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 != p->p_cred->p_ruid ||
|
|
strp->oldgid != p->p_cred->p_rgid)) {
|
|
|
|
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 & P_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 proc *p, uid_t euid)
|
|
{
|
|
struct pcred *pc = p->p_cred;
|
|
uid_t oeuid = pc->pc_ucred->cr_uid;
|
|
|
|
if (pc->pc_ucred->cr_uid == euid)
|
|
return (oeuid);
|
|
|
|
/*
|
|
* Copy credentials so other references do not see our changes.
|
|
*/
|
|
pc->pc_ucred = crcopy(pc->pc_ucred);
|
|
pc->pc_ucred->cr_uid = euid;
|
|
p_sugid(p);
|
|
|
|
return (oeuid);
|
|
}
|
|
|
|
gid_t
|
|
systrace_setegid(struct proc *p, gid_t egid)
|
|
{
|
|
struct pcred *pc = p->p_cred;
|
|
gid_t oegid = pc->pc_ucred->cr_gid;
|
|
|
|
if (pc->pc_ucred->cr_gid == egid)
|
|
return (oegid);
|
|
|
|
/*
|
|
* Copy credentials so other references do not see our changes.
|
|
*/
|
|
pc->pc_ucred = crcopy(pc->pc_ucred);
|
|
pc->pc_ucred->cr_gid = egid;
|
|
p_sugid(p);
|
|
|
|
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)
|
|
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;
|
|
|
|
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;
|
|
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);
|
|
#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;
|
|
|
|
#ifdef __NetBSD__
|
|
error = process_domem(l, proc_representative_lwp(t), &uio);
|
|
#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;
|
|
|
|
if ((proc = pfind(pid)) == NULL) {
|
|
error = ESRCH;
|
|
goto out;
|
|
}
|
|
|
|
if (ISSET(proc->p_flag, P_INEXEC)) {
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* 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, P_SYSTEM)) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* (3) it's being traced already
|
|
*/
|
|
if (ISSET(proc->p_flag, P_SYSTRACE)) {
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* (4) it's not owned by you, or the last exec
|
|
* gave us setuid/setgid privs (unless
|
|
* you're root), or...
|
|
*
|
|
* [Note: once P_SUGID gets set in execve(), it stays
|
|
* set until the process does another execve(). Hence
|
|
* this prevents a setuid process which revokes its
|
|
* special privileges using setuid() from being
|
|
* traced. This is good security.]
|
|
*/
|
|
if ((proc->p_cred->p_ruid != p->p_cred->p_ruid ||
|
|
ISSET(proc->p_flag, P_SUGID)) &&
|
|
(error = suser(p->p_ucred, &p->p_acflag)) != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* (5) ...it's init, which controls the security level
|
|
* of the entire system, and the system was not
|
|
* compiled with permanently insecure mode turned
|
|
* on.
|
|
*/
|
|
if ((proc->p_pid == 1) && (securelevel > -1)) {
|
|
error = EPERM;
|
|
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:
|
|
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 != p->p_cred->p_ruid ||
|
|
fst->p_rgid != p->p_cred->p_rgid) {
|
|
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++) {
|
|
len += repl->strr_offlen[i];
|
|
if (repl->strr_offlen[i] == 0)
|
|
continue;
|
|
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 > 2048)
|
|
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 = (caddr_t)(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;
|
|
caddr_t 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
|
|
|
|
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 = kbase + repl->strr_off[i];
|
|
udata = 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, caddr_t 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, P_SUGID) ||
|
|
fst->p_ruid != p->p_cred->p_ruid ||
|
|
fst->p_rgid != p->p_cred->p_rgid)) {
|
|
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, P_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);
|
|
|
|
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((caddr_t)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, P_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((caddr_t)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 = p->p_cred->p_ruid;
|
|
msg_ugid->gid = p->p_cred->p_rgid;
|
|
|
|
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) {
|
|
int f;
|
|
f = curlwp->l_flag & L_SA;
|
|
curlwp->l_flag &= ~L_SA;
|
|
st = tsleep(strp, PWAIT, "systrmsg", 0);
|
|
curlwp->l_flag |= f;
|
|
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);
|
|
}
|