#include "opt_ktrace.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/sysmsg.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/nlookup.h>
#include <sys/vnode.h>
#include <sys/ktrace.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/sysent.h>
#include <vm/vm_zone.h>
static MALLOC_DEFINE(M_KTRACE, "KTRACE", "KTRACE");
#ifdef KTRACE
static void ktrgetheader (struct ktr_header *kth, int type);
static struct ktr_syscall *ktrgetsyscall(struct ktr_header *kth,
struct ktr_syscall *ktp_cache, int narg);
static void ktrputsyscall(struct ktr_syscall *ktp_cache,
struct ktr_syscall *ktp);
static void ktrwrite (struct lwp *, struct ktr_header *, struct uio *);
static int ktrcanset (struct thread *,struct proc *);
static int ktrsetchildren (struct thread *, struct proc *,
int, int, ktrace_node_t);
static int ktrops (struct thread *,struct proc *,int,int, ktrace_node_t);
static
void
ktrgetheader(struct ktr_header *kth, int type)
{
thread_t td = curthread;
struct proc *p = td->td_proc;
struct lwp *lp = td->td_lwp;
kth->ktr_type = type;
kth->ktr_flags = (p->p_nthreads > 1) ? KTRH_THREADED : 0;
kth->ktr_flags |= KTRH_CPUID_ENCODE(td->td_gd->gd_cpuid);
kth->ktr_pid = p->p_pid;
kth->ktr_tid = lp->lwp_tid;
bcopy(p->p_comm, kth->ktr_comm, MAXCOMLEN + 1);
}
static
struct ktr_syscall *
ktrgetsyscall(struct ktr_header *kth, struct ktr_syscall *ktp_cache, int narg)
{
size_t len;
len = offsetof(struct ktr_syscall, ktr_args[narg]);
if (len > sizeof(*ktp_cache))
ktp_cache = kmalloc(len, M_KTRACE, M_WAITOK);
kth->ktr_buf = (caddr_t)ktp_cache;
kth->ktr_len = (int)len;
return (ktp_cache);
}
static
void
ktrputsyscall(struct ktr_syscall *ktp_cache, struct ktr_syscall *ktp)
{
if (ktp != ktp_cache)
kfree(ktp, M_KTRACE);
}
void
ktrsyscall(struct lwp *lp, int code, int narg, union sysunion *uap)
{
struct ktr_header kth;
struct ktr_syscall ktp_cache;
struct ktr_syscall *ktp;
register_t *argp;
register_t *args = (void *)uap;
int i;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_SYSCALL);
ktp = ktrgetsyscall(&kth, &ktp_cache, narg);
ktp->ktr_code = code;
ktp->ktr_narg = narg;
argp = &ktp->ktr_args[0];
for (i = 0; i < narg; i++)
*argp++ = args[i];
ktrwrite(lp, &kth, NULL);
ktrputsyscall(&ktp_cache, ktp);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrsysret(struct lwp *lp, int code, int error, register_t retval)
{
struct ktr_header kth;
struct ktr_sysret ktp;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_SYSRET);
ktp.ktr_code = code;
ktp.ktr_error = error;
if (error == 0)
ktp.ktr_retval = retval;
else
ktp.ktr_retval = 0;
kth.ktr_buf = (caddr_t)&ktp;
kth.ktr_len = (int)sizeof(struct ktr_sysret);
ktrwrite(lp, &kth, NULL);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrnamei(struct lwp *lp, char *path)
{
struct ktr_header kth;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_NAMEI);
kth.ktr_len = (int)strlen(path);
kth.ktr_buf = path;
ktrwrite(lp, &kth, NULL);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrsysctl(struct lwp *lp, int *name, u_int namelen)
{
struct ktr_header kth;
u_int mib[CTL_MAXNAME + 2];
char *mibname;
size_t mibnamelen;
int error;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
KASSERT(namelen <= CTL_MAXNAME, ("sysctl MIB too long"));
mib[0] = 0;
mib[1] = 1;
bcopy(name, mib + 2, namelen * sizeof(*name));
mibnamelen = 128;
mibname = kmalloc(mibnamelen, M_KTRACE, M_WAITOK);
error = kernel_sysctl(mib, namelen + 2, mibname, &mibnamelen,
NULL, 0, &mibnamelen);
if (error) {
kfree(mibname, M_KTRACE);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
return;
}
ktrgetheader(&kth, KTR_SYSCTL);
kth.ktr_len = mibnamelen;
kth.ktr_buf = mibname;
ktrwrite(lp, &kth, NULL);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrgenio(struct lwp *lp, int fd, enum uio_rw rw, struct uio *uio, int error)
{
struct ktr_header kth;
struct ktr_genio ktg;
if (error)
return;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_GENIO);
ktg.ktr_fd = fd;
ktg.ktr_rw = rw;
kth.ktr_buf = (caddr_t)&ktg;
kth.ktr_len = (int)sizeof(struct ktr_genio);
uio->uio_offset = 0;
uio->uio_rw = UIO_WRITE;
ktrwrite(lp, &kth, uio);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrpsig(struct lwp *lp, int sig, sig_t action, sigset_t *mask, int code)
{
struct ktr_header kth;
struct ktr_psig kp;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_PSIG);
kp.signo = (char)sig;
kp.action = action;
kp.mask = *mask;
kp.code = code;
kth.ktr_buf = (caddr_t)&kp;
kth.ktr_len = (int)sizeof(struct ktr_psig);
ktrwrite(lp, &kth, NULL);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
void
ktrcsw(struct lwp *lp, int out, int user)
{
struct ktr_header kth;
struct ktr_csw kc;
lp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_CSW);
kc.out = out;
kc.user = user;
kth.ktr_buf = (caddr_t)&kc;
kth.ktr_len = (int)sizeof(struct ktr_csw);
ktrwrite(lp, &kth, NULL);
lp->lwp_traceflag &= ~KTRFAC_ACTIVE;
}
#endif
#ifdef KTRACE
struct ktrace_clear_info {
ktrace_node_t tracenode;
int rootclear;
int error;
};
static int ktrace_clear_callback(struct proc *p, void *data);
#endif
int
sys_ktrace(struct sysmsg *sysmsg, const struct ktrace_args *uap)
{
#ifdef KTRACE
struct ktrace_clear_info info;
struct thread *td = curthread;
struct proc *curp = td->td_proc;
struct proc *p;
struct pgrp *pg;
int facs = uap->facs & ~KTRFAC_ROOT;
int ops = KTROP(uap->ops);
int descend = uap->ops & KTRFLAG_DESCEND;
int ret = 0;
int error = 0;
struct nlookupdata nd;
ktrace_node_t tracenode = NULL;
lwkt_gettoken(&curp->p_token);
curp->p_traceflag |= KTRFAC_ACTIVE;
if (ops != KTROP_CLEAR) {
error = nlookup_init(&nd, uap->fname,
UIO_USERSPACE, NLC_LOCKVP);
if (error == 0)
error = vn_open(&nd, NULL, FREAD|FWRITE|O_NOFOLLOW, 0);
if (error == 0 && nd.nl_open_vp->v_type != VREG)
error = EACCES;
if (error) {
curp->p_traceflag &= ~KTRFAC_ACTIVE;
nlookup_done(&nd);
goto done;
}
tracenode = kmalloc(sizeof(struct ktrace_node), M_KTRACE,
M_WAITOK | M_ZERO);
tracenode->kn_vp = nd.nl_open_vp;
tracenode->kn_refs = 1;
nd.nl_open_vp = NULL;
nlookup_done(&nd);
vn_unlock(tracenode->kn_vp);
}
if (ops == KTROP_CLEARFILE) {
info.tracenode = tracenode;
info.error = 0;
info.rootclear = 0;
allproc_scan(ktrace_clear_callback, &info, 0);
error = info.error;
goto done;
}
if (!facs) {
error = EINVAL;
goto done;
}
if (uap->pid < 0) {
pg = pgfind(-uap->pid);
if (pg == NULL) {
error = ESRCH;
goto done;
}
lwkt_gettoken(&pg->pg_token);
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
PHOLD(p);
if (descend)
ret |= ktrsetchildren(td, p, ops, facs, tracenode);
else
ret |= ktrops(td, p, ops, facs, tracenode);
PRELE(p);
}
lwkt_reltoken(&pg->pg_token);
pgrel(pg);
} else {
p = pfind(uap->pid);
if (p == NULL) {
error = ESRCH;
goto done;
}
if (descend)
ret |= ktrsetchildren(td, p, ops, facs, tracenode);
else
ret |= ktrops(td, p, ops, facs, tracenode);
PRELE(p);
}
if (!ret)
error = EPERM;
done:
if (tracenode)
ktrdestroy(&tracenode);
curp->p_traceflag &= ~KTRFAC_ACTIVE;
lwkt_reltoken(&curp->p_token);
return (error);
#else
return ENOSYS;
#endif
}
#ifdef KTRACE
static int
ktrace_clear_callback(struct proc *p, void *data)
{
struct ktrace_clear_info *info = data;
if (p->p_tracenode) {
if (info->rootclear) {
if (p->p_tracenode == info->tracenode) {
ktrdestroy(&p->p_tracenode);
p->p_traceflag = 0;
}
} else {
if (p->p_tracenode->kn_vp == info->tracenode->kn_vp) {
if (ktrcanset(curthread, p)) {
ktrdestroy(&p->p_tracenode);
p->p_traceflag = 0;
} else {
info->error = EPERM;
}
}
}
}
return(0);
}
#endif
int
sys_utrace(struct sysmsg *sysmsg, const struct utrace_args *uap)
{
#ifdef KTRACE
struct ktr_header kth;
struct thread *td = curthread;
char cp_cache[64];
caddr_t cp;
if (!KTRPOINT(td, KTR_USER))
return (0);
if (uap->len > KTR_USER_MAXLEN)
return (EINVAL);
td->td_lwp->lwp_traceflag |= KTRFAC_ACTIVE;
ktrgetheader(&kth, KTR_USER);
if (uap->len <= sizeof(cp_cache))
cp = cp_cache;
else
cp = kmalloc(uap->len, M_KTRACE, M_WAITOK);
if (!copyin(uap->addr, cp, uap->len)) {
kth.ktr_buf = cp;
kth.ktr_len = uap->len;
ktrwrite(td->td_lwp, &kth, NULL);
}
if (cp != cp_cache)
kfree(cp, M_KTRACE);
td->td_lwp->lwp_traceflag &= ~KTRFAC_ACTIVE;
return (0);
#else
return (ENOSYS);
#endif
}
void
ktrdestroy(struct ktrace_node **tracenodep)
{
ktrace_node_t tracenode;
if ((tracenode = *tracenodep) != NULL) {
*tracenodep = NULL;
KKASSERT(tracenode->kn_refs > 0);
if (atomic_fetchadd_int(&tracenode->kn_refs, -1) == 1) {
vn_close(tracenode->kn_vp, FREAD|FWRITE, NULL);
tracenode->kn_vp = NULL;
kfree(tracenode, M_KTRACE);
}
}
}
ktrace_node_t
ktrinherit(ktrace_node_t tracenode)
{
if (tracenode) {
KKASSERT(tracenode->kn_refs > 0);
atomic_add_int(&tracenode->kn_refs, 1);
}
return(tracenode);
}
#ifdef KTRACE
static int
ktrops(struct thread *td, struct proc *p, int ops, int facs,
ktrace_node_t tracenode)
{
ktrace_node_t oldnode;
if (!ktrcanset(td, p))
return (0);
if (ops == KTROP_SET) {
if ((oldnode = p->p_tracenode) != tracenode) {
p->p_tracenode = ktrinherit(tracenode);
ktrdestroy(&oldnode);
}
p->p_traceflag |= facs;
if (td->td_ucred->cr_uid == 0)
p->p_traceflag |= KTRFAC_ROOT;
} else {
if (((p->p_traceflag &= ~facs) & KTRFAC_MASK) == 0) {
p->p_traceflag = 0;
ktrdestroy(&p->p_tracenode);
}
}
return (1);
}
static int
ktrsetchildren(struct thread *td, struct proc *top, int ops, int facs,
ktrace_node_t tracenode)
{
struct proc *p;
struct proc *np;
int ret = 0;
p = top;
PHOLD(p);
lwkt_gettoken(&p->p_token);
for (;;) {
ret |= ktrops(td, p, ops, facs, tracenode);
if ((np = LIST_FIRST(&p->p_children)) != NULL) {
PHOLD(np);
}
while (np == NULL) {
if (p == top)
break;
if ((np = LIST_NEXT(p, p_sibling)) != NULL) {
PHOLD(np);
break;
}
np = p->p_pptr;
if (np == NULL)
break;
PHOLD(np);
lwkt_reltoken(&p->p_token);
PRELE(p);
p = np;
lwkt_gettoken(&p->p_token);
np = NULL;
}
lwkt_reltoken(&p->p_token);
PRELE(p);
p = np;
if (p == NULL)
break;
lwkt_gettoken(&p->p_token);
}
return (ret);
}
static void
ktrwrite(struct lwp *lp, struct ktr_header *kth, struct uio *uio)
{
struct ktrace_clear_info info;
struct uio auio;
struct iovec aiov[2];
int error;
ktrace_node_t tracenode;
if (lp->lwp_proc->p_tracenode == NULL)
return;
tracenode = ktrinherit(lp->lwp_proc->p_tracenode);
auio.uio_iov = &aiov[0];
auio.uio_offset = 0;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_rw = UIO_WRITE;
aiov[0].iov_base = (caddr_t)kth;
aiov[0].iov_len = sizeof(struct ktr_header);
auio.uio_resid = sizeof(struct ktr_header);
auio.uio_iovcnt = 1;
auio.uio_td = curthread;
if (kth->ktr_len > 0) {
auio.uio_iovcnt++;
aiov[1].iov_base = kth->ktr_buf;
aiov[1].iov_len = kth->ktr_len;
auio.uio_resid += kth->ktr_len;
if (uio != NULL)
kth->ktr_len += uio->uio_resid;
}
vn_lock(tracenode->kn_vp, LK_EXCLUSIVE | LK_RETRY);
microtime(&kth->ktr_time);
error = VOP_WRITE(tracenode->kn_vp, &auio,
IO_UNIT | IO_APPEND, lp->lwp_thread->td_ucred);
if (error == 0 && uio != NULL) {
error = VOP_WRITE(tracenode->kn_vp, uio,
IO_UNIT | IO_APPEND, lp->lwp_thread->td_ucred);
}
vn_unlock(tracenode->kn_vp);
if (error) {
log(LOG_NOTICE,
"ktrace write failed, errno %d, tracing stopped\n", error);
info.tracenode = tracenode;
info.error = 0;
info.rootclear = 1;
allproc_scan(ktrace_clear_callback, &info, 0);
}
ktrdestroy(&tracenode);
}
static int
ktrcanset(struct thread *calltd, struct proc *targetp)
{
struct ucred *caller = calltd->td_ucred;
struct ucred *target = targetp->p_ucred;
if (!PRISON_CHECK(caller, target))
return (0);
if ((caller->cr_uid == target->cr_ruid &&
target->cr_ruid == target->cr_svuid &&
caller->cr_rgid == target->cr_rgid &&
target->cr_rgid == target->cr_svgid &&
(targetp->p_traceflag & KTRFAC_ROOT) == 0 &&
(targetp->p_flags & P_SUGID) == 0) ||
caller->cr_uid == 0)
return (1);
return (0);
}
#endif