/* $NetBSD: filemon.c,v 1.28 2016/01/11 01:37:36 pgoyette Exp $ */ /* * Copyright (c) 2010, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: filemon.c,v 1.28 2016/01/11 01:37:36 pgoyette Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filemon.h" #include "ioconf.h" MODULE(MODULE_CLASS_DRIVER, filemon, NULL); static dev_type_open(filemon_open); struct cdevsw filemon_cdevsw = { .d_open = filemon_open, .d_close = noclose, .d_read = noread, .d_write = nowrite, .d_ioctl = noioctl, .d_stop = nostop, .d_tty = notty, .d_poll = nopoll, .d_mmap = nommap, .d_kqfilter = nokqfilter, .d_discard = nodiscard, .d_flag = D_MPSAFE }; static int filemon_ioctl(struct file *, u_long, void *); static int filemon_close(struct file *); static const struct fileops filemon_fileops = { .fo_ioctl = filemon_ioctl, .fo_close = filemon_close, .fo_read = fbadop_read, .fo_write = fbadop_write, .fo_fcntl = fnullop_fcntl, .fo_poll = fnullop_poll, .fo_stat = fbadop_stat, .fo_kqfilter = fnullop_kqfilter, }; static krwlock_t filemon_mtx; static TAILQ_HEAD(, filemon) filemons_inuse = TAILQ_HEAD_INITIALIZER(filemons_inuse); #ifdef DEBUG static int logLevel = LOG_DEBUG; #endif void filemon_output(struct filemon * filemon, char *msg, size_t len) { struct uio auio; struct iovec aiov; if (filemon->fm_fp == NULL) return; aiov.iov_base = msg; aiov.iov_len = len; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_resid = len; auio.uio_rw = UIO_WRITE; auio.uio_offset = (off_t) - 1; uio_setup_sysspace(&auio); #ifdef DEBUG { char *cp; int x = 16; cp = strchr(msg, '\n'); if (cp && cp - msg <= 16) x = (cp - msg) - 2; log(logLevel, "filemon_output:('%.*s%s'", x, (x < 16) ? "..." : "", msg); } #endif (*filemon->fm_fp->f_ops->fo_write) (filemon->fm_fp, &(filemon->fm_fp->f_offset), &auio, curlwp->l_cred, FOF_UPDATE_OFFSET); } void filemon_printf(struct filemon *filemon, const char *fmt, ...) { size_t len; va_list ap; va_start(ap, fmt); len = vsnprintf(filemon->fm_msgbufr, sizeof(filemon->fm_msgbufr), fmt, ap); va_end(ap); if (len > sizeof(filemon->fm_msgbufr)) len = sizeof(filemon->fm_msgbufr); filemon_output(filemon, filemon->fm_msgbufr, len); } static void filemon_comment(struct filemon * filemon) { filemon_printf(filemon, "# filemon version %d\n# Target pid %d\nV %d\n", FILEMON_VERSION, curproc->p_pid, FILEMON_VERSION); } static struct filemon * filemon_pid_check(struct proc * p) { struct filemon *filemon; struct proc * lp; KASSERT(p != NULL); if (!TAILQ_EMPTY(&filemons_inuse)) { /* * make sure p cannot exit * until we have moved on to p_pptr */ rw_enter(&p->p_reflock, RW_READER); while (p) { TAILQ_FOREACH(filemon, &filemons_inuse, fm_link) { if (p->p_pid == filemon->fm_pid) { rw_exit(&p->p_reflock); return (filemon); } } lp = p; p = p->p_pptr; /* lock parent before releasing child */ if (p != NULL) rw_enter(&p->p_reflock, RW_READER); rw_exit(&lp->p_reflock); } } return (NULL); } /* * return exclusive access to a filemon struct */ struct filemon * filemon_lookup(struct proc * p) { struct filemon *filemon; rw_enter(&filemon_mtx, RW_READER); filemon = filemon_pid_check(p); if (filemon) { rw_enter(&filemon->fm_mtx, RW_WRITER); } rw_exit(&filemon_mtx); return filemon; } static struct filemon * filemon_fp_data(struct file * fp, int lck) { struct filemon *filemon; rw_enter(&filemon_mtx, RW_READER); filemon = fp->f_data; if (filemon && lck) { rw_enter(&filemon->fm_mtx, lck); } rw_exit(&filemon_mtx); return filemon; } static int n_open = 0; static int filemon_open(dev_t dev, int oflags __unused, int mode __unused, struct lwp * l __unused) { struct filemon *filemon; struct file *fp; int error, fd; /* falloc() will fill in the descriptor for us. */ if ((error = fd_allocfile(&fp, &fd)) != 0) return error; filemon = kmem_alloc(sizeof(struct filemon), KM_SLEEP); rw_init(&filemon->fm_mtx); filemon->fm_fp = NULL; filemon->fm_pid = curproc->p_pid; rw_enter(&filemon_mtx, RW_WRITER); TAILQ_INSERT_TAIL(&filemons_inuse, filemon, fm_link); n_open++; rw_exit(&filemon_mtx); return fd_clone(fp, fd, oflags, &filemon_fileops, filemon); } static int filemon_close(struct file * fp) { struct filemon *filemon; #ifdef DEBUG log(logLevel, "filemon_close()"); #endif /* * Follow the same lock order as filemon_lookup() * and filemon_fp_data() but hold exclusive access to * filemon_mtx until we are done. */ rw_enter(&filemon_mtx, RW_WRITER); filemon = fp->f_data; if (!filemon) { rw_exit(&filemon_mtx); return EBADF; } /* ensure that filemon_lookup() will now fail */ TAILQ_REMOVE(&filemons_inuse, filemon, fm_link); n_open--; /* ensure that filemon_fp_data() will now fail */ fp->f_data = NULL; /* * once we have exclusive access, it should never be used again */ rw_enter(&filemon->fm_mtx, RW_WRITER); if (filemon->fm_fp) { closef(filemon->fm_fp); /* release our reference */ filemon->fm_fp = NULL; } rw_exit(&filemon->fm_mtx); rw_destroy(&filemon->fm_mtx); kmem_free(filemon, sizeof(struct filemon)); rw_exit(&filemon_mtx); return (0); } static int filemon_ioctl(struct file * fp, u_long cmd, void *data) { int error = 0; int fd; struct filemon *filemon; struct proc *tp; #ifdef DEBUG log(logLevel, "filemon_ioctl(%lu)", cmd);; #endif /* * this ensures we cannot get filemon if it is closing. */ filemon = filemon_fp_data(fp, RW_WRITER); if (!filemon) return EBADF; /* filemon_fp_data() has locked the entry - make sure to unlock! */ switch (cmd) { case FILEMON_SET_FD: /* Set the output file descriptor. */ /* First, release any current output file descriptor */ if (filemon->fm_fp) closef(filemon->fm_fp); /* Now set up the new one */ fd = *((int *) data); if ((filemon->fm_fp = fd_getfile2(curproc, fd)) == NULL) { error = EBADF; break; } /* Write the file header. */ filemon_comment(filemon); break; case FILEMON_SET_PID: /* Set the monitored process ID - if allowed. */ mutex_enter(proc_lock); tp = proc_find(*((pid_t *) data)); if (tp == NULL || tp->p_emul != &emul_netbsd) { error = ESRCH; mutex_exit(proc_lock); break; } error = kauth_authorize_process(curproc->p_cred, KAUTH_PROCESS_CANSEE, tp, KAUTH_ARG(KAUTH_REQ_PROCESS_CANSEE_ENTRY), NULL, NULL); if (!error) { filemon->fm_pid = tp->p_pid; } mutex_exit(proc_lock); break; default: error = EINVAL; break; } rw_exit(&filemon->fm_mtx); return (error); } static int filemon_load(void *dummy __unused) { rw_init(&filemon_mtx); /* Install the syscall wrappers. */ return filemon_wrapper_install(); } /* * If this gets called we are linked into the kernel */ void filemonattach(int num) { /* * Don't call filemon_load() here - it will be called from * filemon_modcmd() during module initialization. */ #if 0 filemon_load(NULL); #endif } static int filemon_unload(void) { int error = 0; rw_enter(&filemon_mtx, RW_WRITER); if (TAILQ_FIRST(&filemons_inuse) != NULL) error = EBUSY; else { /* Deinstall the syscall wrappers. */ error = filemon_wrapper_deinstall(); } rw_exit(&filemon_mtx); if (error == 0) { rw_destroy(&filemon_mtx); } return (error); } static int filemon_modcmd(modcmd_t cmd, void *data) { int error = 0; #ifdef _MODULE int bmajor = -1; int cmajor = -1; #endif switch (cmd) { case MODULE_CMD_INIT: #ifdef DEBUG logLevel = LOG_INFO; #endif error = filemon_load(data); #ifdef _MODULE if (!error) error = devsw_attach("filemon", NULL, &bmajor, &filemon_cdevsw, &cmajor); #endif break; case MODULE_CMD_FINI: error = filemon_unload(); #ifdef _MODULE if (!error) error = devsw_detach(NULL, &filemon_cdevsw); #endif break; case MODULE_CMD_STAT: log(LOG_INFO, "filemon: open=%d", n_open); break; default: error = ENOTTY; break; } return (error); }