Index: head/share/man/man4/filemon.4 =================================================================== --- head/share/man/man4/filemon.4 (revision 297200) +++ head/share/man/man4/filemon.4 (revision 297201) @@ -1,226 +1,232 @@ .\" Copyright (c) 2012 .\" David E. O'Brien . 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 acknowledgment: .\" This product includes software developed by David E. O'Brien and .\" contributors. .\" 4. Neither the name of the author nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANT ABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd March 22, 2016 .Dt FILEMON 4 .Os .Sh NAME .Nm filemon .Nd the filemon device .Sh SYNOPSIS .In dev/filemon/filemon.h .Sh DESCRIPTION The .Nm device allows a process to collect file operations data of its children. The device .Pa /dev/filemon responds to two .Xr ioctl 2 calls. .Pp .Nm is not intended to be a security auditing tool. Many system calls are not tracked and binaries of foreign ABI will not be fully audited. It is intended for auditing of processes for the purpose of determining its dependencies in an efficient and easily parsable format. An example of this is .Xr make 1 which uses this module with .Sy .MAKE.MODE=meta to handle incremental builds more smartly. .Pp System calls are denoted using the following single letters: .Pp .Bl -tag -width indent -compact .It Ql A .Xr openat 2 . The next log entry may be lacking an absolute path or be inaccurate. .It Ql C .Xr chdir 2 .It Ql D .Xr unlink 2 .It Ql E .Xr exec 2 .It Ql F .Xr fork 2 , .Xr vfork 2 .It Ql L .Xr link 2 , .Xr linkat 2 , .Xr symlink 2 , .Xr symlinkat 2 .It Ql M .Xr rename 2 .It Ql R .Xr open 2 or .Xr openat 2 for read .It Ql W .Xr open 2 or .Xr openat 2 for write .It Ql X .Xr _exit 2 .El .Pp Note that .Ql R following .Ql W records can represent a single .Xr open 2 for R/W, or two separate .Xr open 2 calls, one for .Ql R and one for .Ql W . Note that only successful system calls are captured. .Sh IOCTLS User mode programs communicate with the .Nm driver through a number of ioctls which are described below. Each takes a single argument. .Bl -tag -width ".Dv FILEMON_SET_PID" .It Dv FILEMON_SET_FD Write the internal tracing buffer to the supplied open file descriptor. .It Dv FILEMON_SET_PID Child process ID to trace. This should normally be done under the control of a parent in the child after .Xr fork 2 but before anything else. See the example below. .El .Sh RETURN VALUES .\" .Rv -std ioctl The .Fn ioctl function returns the value 0 if successful; otherwise the value \-1 is returned and the global variable .Va errno is set to indicate the error. .Sh ERRORS The .Fn ioctl system call with .Dv FILEMON_SET_FD will fail if: .Bl -tag -width Er .It Bq Er EEXIST The .Nm handle is already associated with a file descriptor. .El .Pp The .Fn ioctl system call with .Dv FILEMON_SET_PID will fail if: .Bl -tag -width Er .It Bq Er ESRCH No process having the specified process ID exists. .It Bq Er EBUSY The process ID specified is already being traced and was not the current process. .El +.Pp +The +.Fn close +system call on the filemon file descriptor may fail with the errors from +.Xr write 2 +if any error is encountered while writing the log. .Sh FILES .Bl -tag -width ".Pa /dev/filemon" .It Pa /dev/filemon .El .Sh EXAMPLES .Bd -literal #include #include #include #include #include #include #include #include static void open_filemon(void) { pid_t child; int fm_fd, fm_log; if ((fm_fd = open("/dev/filemon", O_RDWR | O_CLOEXEC)) == -1) err(1, "open(\e"/dev/filemon\e", O_RDWR)"); if ((fm_log = open("filemon.out", O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, DEFFILEMODE)) == -1) err(1, "open(filemon.out)"); if (ioctl(fm_fd, FILEMON_SET_FD, &fm_log) == -1) err(1, "Cannot set filemon log file descriptor"); if ((child = fork()) == 0) { child = getpid(); if (ioctl(fm_fd, FILEMON_SET_PID, &child) == -1) err(1, "Cannot set filemon PID"); /* Do something here. */ } else { wait(&child); close(fm_fd); } } .Ed .Pp Creates a file named .Pa filemon.out and configures the .Nm device to write the .Nm buffer contents to it. .Sh SEE ALSO .Xr dtrace 1 , .Xr ktrace 1 , .Xr script 1 , .Xr truss 1 , .Xr ioctl 2 .Sh HISTORY A .Nm device appeared in .Fx 9.1 . .Sh BUGS Unloading the module may panic the system, thus requires using .Ic kldunload -f . Index: head/sys/dev/filemon/filemon.c =================================================================== --- head/sys/dev/filemon/filemon.c (revision 297200) +++ head/sys/dev/filemon/filemon.c (revision 297201) @@ -1,489 +1,509 @@ /*- * Copyright (c) 2011, David E. O'Brien. * Copyright (c) 2009-2011, Juniper Networks, Inc. * Copyright (c) 2015-2016, EMC Corp. * 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. * * THIS SOFTWARE IS PROVIDED BY JUNIPER NETWORKS 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 JUNIPER NETWORKS 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 __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filemon.h" #if defined(COMPAT_IA32) || defined(COMPAT_FREEBSD32) || defined(COMPAT_ARCH32) #include #include extern struct sysentvec ia32_freebsd_sysvec; #endif extern struct sysentvec elf32_freebsd_sysvec; extern struct sysentvec elf64_freebsd_sysvec; static d_close_t filemon_close; static d_ioctl_t filemon_ioctl; static d_open_t filemon_open; static struct cdevsw filemon_cdevsw = { .d_version = D_VERSION, .d_close = filemon_close, .d_ioctl = filemon_ioctl, .d_open = filemon_open, .d_name = "filemon", }; MALLOC_DECLARE(M_FILEMON); MALLOC_DEFINE(M_FILEMON, "filemon", "File access monitor"); /* * The filemon->lock protects several things currently: * - fname1/fname2/msgbufr are pre-allocated and used per syscall * for logging and copyins rather than stack variables. * - Serializing the filemon's log output. * - Preventing inheritance or removal of the filemon into proc.p_filemon. */ struct filemon { struct sx lock; /* Lock for this filemon. */ struct file *fp; /* Output file pointer. */ char fname1[MAXPATHLEN]; /* Temporary filename buffer. */ char fname2[MAXPATHLEN]; /* Temporary filename buffer. */ char msgbufr[1024]; /* Output message buffer. */ + int error; /* Log write error, returned on close(2). */ u_int refcnt; /* Pointer reference count. */ u_int proccnt; /* Process count. */ }; static struct cdev *filemon_dev; static void filemon_output(struct filemon *filemon, char *msg, size_t len); static __inline struct filemon * filemon_acquire(struct filemon *filemon) { if (filemon != NULL) refcount_acquire(&filemon->refcnt); return (filemon); } /* * Release a reference and free on the last one. */ static void filemon_release(struct filemon *filemon) { if (refcount_release(&filemon->refcnt) == 0) return; /* * There are valid cases of releasing while locked, such as in * filemon_untrack_processes, but none which are done where there * is not at least 1 reference remaining. */ sx_assert(&filemon->lock, SA_UNLOCKED); sx_destroy(&filemon->lock); free(filemon, M_FILEMON); } /* * Acquire the proc's p_filemon reference and lock the filemon. * The proc's p_filemon may not match this filemon on return. */ static struct filemon * filemon_proc_get(struct proc *p) { struct filemon *filemon; PROC_LOCK(p); filemon = filemon_acquire(p->p_filemon); PROC_UNLOCK(p); if (filemon == NULL) return (NULL); /* * The p->p_filemon may have changed by now. That case is handled * by the exit and fork hooks and filemon_attach_proc specially. */ sx_xlock(&filemon->lock); return (filemon); } /* Remove and release the filemon on the given process. */ static void filemon_proc_drop(struct proc *p) { struct filemon *filemon; KASSERT(p->p_filemon != NULL, ("%s: proc %p NULL p_filemon", __func__, p)); sx_assert(&p->p_filemon->lock, SA_XLOCKED); PROC_LOCK(p); filemon = p->p_filemon; p->p_filemon = NULL; --filemon->proccnt; PROC_UNLOCK(p); /* * This should not be the last reference yet. filemon_release() * cannot be called with filemon locked, which the caller expects * will stay locked. */ KASSERT(filemon->refcnt > 1, ("%s: proc %p dropping filemon %p " "with last reference", __func__, p, filemon)); filemon_release(filemon); } /* Unlock and release the filemon. */ static __inline void filemon_drop(struct filemon *filemon) { sx_xunlock(&filemon->lock); filemon_release(filemon); } #include "filemon_wrapper.c" static void filemon_comment(struct filemon *filemon) { int len; struct timeval now; getmicrotime(&now); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "# filemon version %d\n# Target pid %d\n# Start %ju.%06ju\nV %d\n", FILEMON_VERSION, curproc->p_pid, (uintmax_t)now.tv_sec, (uintmax_t)now.tv_usec, FILEMON_VERSION); filemon_output(filemon, filemon->msgbufr, len); } /* * Invalidate the passed filemon in all processes. */ static void filemon_untrack_processes(struct filemon *filemon) { struct proc *p; sx_assert(&filemon->lock, SA_XLOCKED); /* Avoid allproc loop if there is no need. */ if (filemon->proccnt == 0) return; /* * Processes in this list won't go away while here since * filemon_event_process_exit() will lock on filemon->lock * which we hold. */ sx_slock(&allproc_lock); FOREACH_PROC_IN_SYSTEM(p) { /* * No PROC_LOCK is needed to compare here since it is * guaranteed to not change since we have its filemon * locked. Everything that changes this p_filemon will * be locked on it. */ if (p->p_filemon == filemon) filemon_proc_drop(p); } sx_sunlock(&allproc_lock); /* * It's possible some references were acquired but will be * dropped shortly as they are restricted from being * inherited. There is at least the reference in cdevpriv remaining. */ KASSERT(filemon->refcnt > 0, ("%s: filemon %p should have " "references still.", __func__, filemon)); KASSERT(filemon->proccnt == 0, ("%s: filemon %p should not have " "attached procs still.", __func__, filemon)); } /* * Close out the log. */ static void filemon_close_log(struct filemon *filemon) { struct file *fp; struct timeval now; size_t len; sx_assert(&filemon->lock, SA_XLOCKED); if (filemon->fp == NULL) return; getmicrotime(&now); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "# Stop %ju.%06ju\n# Bye bye\n", (uintmax_t)now.tv_sec, (uintmax_t)now.tv_usec); filemon_output(filemon, filemon->msgbufr, len); fp = filemon->fp; filemon->fp = NULL; sx_xunlock(&filemon->lock); fdrop(fp, curthread); sx_xlock(&filemon->lock); return; } -/* The devfs file is being closed. Untrace all processes. */ +/* + * The devfs file is being closed. Untrace all processes. It is possible + * filemon_close/close(2) was not called. + */ static void filemon_dtr(void *data) { struct filemon *filemon = data; if (filemon == NULL) return; sx_xlock(&filemon->lock); /* * Detach the filemon. It cannot be inherited after this. */ filemon_untrack_processes(filemon); filemon_close_log(filemon); filemon_drop(filemon); } /* Attach the filemon to the process. */ static int filemon_attach_proc(struct filemon *filemon, struct proc *p) { struct filemon *filemon2; sx_assert(&filemon->lock, SA_XLOCKED); PROC_LOCK_ASSERT(p, MA_OWNED); KASSERT((p->p_flag & P_WEXIT) == 0, ("%s: filemon %p attaching to exiting process %p", __func__, filemon, p)); if (p->p_filemon == filemon) return (0); /* * Don't allow truncating other process traces. It is * not really intended to trace procs other than curproc * anyhow. */ if (p->p_filemon != NULL && p != curproc) return (EBUSY); /* * Historic behavior of filemon has been to let a child initiate * tracing on itself and cease existing tracing. Bmake * .META + .MAKE relies on this. It is only relevant for attaching to * curproc. */ while (p->p_filemon != NULL) { PROC_UNLOCK(p); sx_xunlock(&filemon->lock); while ((filemon2 = filemon_proc_get(p)) != NULL) { /* It may have changed. */ if (p->p_filemon == filemon2) filemon_proc_drop(p); filemon_drop(filemon2); } sx_xlock(&filemon->lock); PROC_LOCK(p); /* * It may have been attached to, though unlikely. * Try again if needed. */ } KASSERT(p->p_filemon == NULL, ("%s: proc %p didn't detach filemon %p", __func__, p, p->p_filemon)); p->p_filemon = filemon_acquire(filemon); ++filemon->proccnt; return (0); } static int filemon_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag __unused, struct thread *td) { int error = 0; struct filemon *filemon; struct proc *p; cap_rights_t rights; if ((error = devfs_get_cdevpriv((void **) &filemon)) != 0) return (error); sx_xlock(&filemon->lock); switch (cmd) { /* Set the output file descriptor. */ case FILEMON_SET_FD: if (filemon->fp != NULL) { error = EEXIST; break; } error = fget_write(td, *(int *)data, cap_rights_init(&rights, CAP_PWRITE), &filemon->fp); if (error == 0) /* Write the file header. */ filemon_comment(filemon); break; /* Set the monitored process ID. */ case FILEMON_SET_PID: /* Invalidate any existing processes already set. */ filemon_untrack_processes(filemon); error = pget(*((pid_t *)data), PGET_CANDEBUG | PGET_NOTWEXIT, &p); if (error == 0) { KASSERT(p->p_filemon != filemon, ("%s: proc %p didn't untrack filemon %p", __func__, p, filemon)); error = filemon_attach_proc(filemon, p); PROC_UNLOCK(p); } break; default: error = EINVAL; break; } sx_xunlock(&filemon->lock); return (error); } static int filemon_open(struct cdev *dev, int oflags __unused, int devtype __unused, struct thread *td __unused) { int error; struct filemon *filemon; filemon = malloc(sizeof(*filemon), M_FILEMON, M_WAITOK | M_ZERO); sx_init(&filemon->lock, "filemon"); refcount_init(&filemon->refcnt, 1); error = devfs_set_cdevpriv(filemon, filemon_dtr); if (error != 0) filemon_release(filemon); return (error); } +/* Called on close of last devfs file handle, before filemon_dtr(). */ static int filemon_close(struct cdev *dev __unused, int flag __unused, int fmt __unused, struct thread *td __unused) { + struct filemon *filemon; + int error; - return (0); + if ((error = devfs_get_cdevpriv((void **) &filemon)) != 0) + return (error); + + sx_xlock(&filemon->lock); + filemon_close_log(filemon); + error = filemon->error; + sx_xunlock(&filemon->lock); + /* + * Processes are still being traced but won't log anything + * now. After this call returns filemon_dtr() is called which + * will detach processes. + */ + + return (error); } static void filemon_load(void *dummy __unused) { /* Install the syscall wrappers. */ filemon_wrapper_install(); filemon_dev = make_dev(&filemon_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "filemon"); } static int filemon_unload(void) { destroy_dev(filemon_dev); filemon_wrapper_deinstall(); return (0); } static int filemon_modevent(module_t mod __unused, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: filemon_load(data); break; case MOD_UNLOAD: error = filemon_unload(); break; case MOD_QUIESCE: /* * The wrapper implementation is unsafe for reliable unload. * Require forcing an unload. */ error = EBUSY; break; case MOD_SHUTDOWN: break; default: error = EOPNOTSUPP; break; } return (error); } DEV_MODULE(filemon, filemon_modevent, NULL); MODULE_VERSION(filemon, 1); Index: head/sys/dev/filemon/filemon_wrapper.c =================================================================== --- head/sys/dev/filemon/filemon_wrapper.c (revision 297200) +++ head/sys/dev/filemon/filemon_wrapper.c (revision 297201) @@ -1,459 +1,462 @@ /*- * Copyright (c) 2011, David E. O'Brien. * Copyright (c) 2009-2011, Juniper Networks, Inc. * Copyright (c) 2015-2016, EMC Corp. * 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. * * THIS SOFTWARE IS PROVIDED BY JUNIPER NETWORKS 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 JUNIPER NETWORKS 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "opt_compat.h" static eventhandler_tag filemon_exec_tag; static eventhandler_tag filemon_exit_tag; static eventhandler_tag filemon_fork_tag; static void filemon_output(struct filemon *filemon, char *msg, size_t len) { struct uio auio; struct iovec aiov; + int error; if (filemon->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_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_WRITE; auio.uio_td = curthread; auio.uio_offset = (off_t) -1; if (filemon->fp->f_type == DTYPE_VNODE) bwillwrite(); - fo_write(filemon->fp, &auio, curthread->td_ucred, 0, curthread); + error = fo_write(filemon->fp, &auio, curthread->td_ucred, 0, curthread); + if (error != 0) + filemon->error = error; } static int filemon_wrapper_chdir(struct thread *td, struct chdir_args *uap) { int ret; size_t done; size_t len; struct filemon *filemon; if ((ret = sys_chdir(td, uap)) == 0) { if ((filemon = filemon_proc_get(curproc)) != NULL) { copyinstr(uap->path, filemon->fname1, sizeof(filemon->fname1), &done); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "C %d %s\n", curproc->p_pid, filemon->fname1); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); } } return (ret); } static void filemon_event_process_exec(void *arg __unused, struct proc *p, struct image_params *imgp) { struct filemon *filemon; char *fullpath, *freepath; size_t len; if ((filemon = filemon_proc_get(p)) != NULL) { fullpath = ""; freepath = NULL; vn_fullpath(curthread, imgp->vp, &fullpath, &freepath); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "E %d %s\n", p->p_pid, fullpath); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); free(freepath, M_TEMP); } } static void _filemon_wrapper_openat(struct thread *td, char *upath, int flags, int fd) { size_t done; size_t len; struct file *fp; struct filemon *filemon; char *atpath, *freepath; cap_rights_t rights; if ((filemon = filemon_proc_get(curproc)) != NULL) { atpath = ""; freepath = NULL; fp = NULL; copyinstr(upath, filemon->fname1, sizeof(filemon->fname1), &done); if (filemon->fname1[0] != '/' && fd != AT_FDCWD) { /* * rats - we cannot do too much about this. * the trace should show a dir we read * recently.. output an A record as a clue * until we can do better. * XXX: This may be able to come out with * the namecache lookup now. */ len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "A %d %s\n", curproc->p_pid, filemon->fname1); filemon_output(filemon, filemon->msgbufr, len); /* * Try to resolve the path from the vnode using the * namecache. It may be inaccurate, but better * than nothing. */ if (getvnode(td, fd, cap_rights_init(&rights, CAP_LOOKUP), &fp) == 0) { vn_fullpath(td, fp->f_vnode, &atpath, &freepath); } } if (flags & O_RDWR) { /* * We'll get the W record below, but need * to also output an R to distinguish from * O_WRONLY. */ len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "R %d %s%s%s\n", curproc->p_pid, atpath, atpath[0] != '\0' ? "/" : "", filemon->fname1); filemon_output(filemon, filemon->msgbufr, len); } len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "%c %d %s%s%s\n", (flags & O_ACCMODE) ? 'W':'R', curproc->p_pid, atpath, atpath[0] != '\0' ? "/" : "", filemon->fname1); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); if (fp != NULL) fdrop(fp, td); free(freepath, M_TEMP); } } static int filemon_wrapper_open(struct thread *td, struct open_args *uap) { int ret; if ((ret = sys_open(td, uap)) == 0) _filemon_wrapper_openat(td, uap->path, uap->flags, AT_FDCWD); return (ret); } static int filemon_wrapper_openat(struct thread *td, struct openat_args *uap) { int ret; if ((ret = sys_openat(td, uap)) == 0) _filemon_wrapper_openat(td, uap->path, uap->flag, uap->fd); return (ret); } static int filemon_wrapper_rename(struct thread *td, struct rename_args *uap) { int ret; size_t done; size_t len; struct filemon *filemon; if ((ret = sys_rename(td, uap)) == 0) { if ((filemon = filemon_proc_get(curproc)) != NULL) { copyinstr(uap->from, filemon->fname1, sizeof(filemon->fname1), &done); copyinstr(uap->to, filemon->fname2, sizeof(filemon->fname2), &done); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "M %d '%s' '%s'\n", curproc->p_pid, filemon->fname1, filemon->fname2); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); } } return (ret); } static void _filemon_wrapper_link(struct thread *td, char *upath1, char *upath2) { struct filemon *filemon; size_t len; if ((filemon = filemon_proc_get(curproc)) != NULL) { copyinstr(upath1, filemon->fname1, sizeof(filemon->fname1), NULL); copyinstr(upath2, filemon->fname2, sizeof(filemon->fname2), NULL); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "L %d '%s' '%s'\n", curproc->p_pid, filemon->fname1, filemon->fname2); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); } } static int filemon_wrapper_link(struct thread *td, struct link_args *uap) { int ret; if ((ret = sys_link(td, uap)) == 0) _filemon_wrapper_link(td, uap->path, uap->link); return (ret); } static int filemon_wrapper_symlink(struct thread *td, struct symlink_args *uap) { int ret; if ((ret = sys_symlink(td, uap)) == 0) _filemon_wrapper_link(td, uap->path, uap->link); return (ret); } static int filemon_wrapper_linkat(struct thread *td, struct linkat_args *uap) { int ret; if ((ret = sys_linkat(td, uap)) == 0) _filemon_wrapper_link(td, uap->path1, uap->path2); return (ret); } static void filemon_event_process_exit(void *arg __unused, struct proc *p) { size_t len; struct filemon *filemon; if ((filemon = filemon_proc_get(p)) != NULL) { len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "X %d %d %d\n", p->p_pid, p->p_xexit, p->p_xsig); filemon_output(filemon, filemon->msgbufr, len); /* * filemon_untrack_processes() may have dropped this p_filemon * already while in filemon_proc_get() before acquiring the * filemon lock. */ KASSERT(p->p_filemon == NULL || p->p_filemon == filemon, ("%s: p %p was attached while exiting, expected " "filemon %p or NULL", __func__, p, filemon)); if (p->p_filemon == filemon) filemon_proc_drop(p); filemon_drop(filemon); } } static int filemon_wrapper_unlink(struct thread *td, struct unlink_args *uap) { int ret; size_t done; size_t len; struct filemon *filemon; if ((ret = sys_unlink(td, uap)) == 0) { if ((filemon = filemon_proc_get(curproc)) != NULL) { copyinstr(uap->path, filemon->fname1, sizeof(filemon->fname1), &done); len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "D %d %s\n", curproc->p_pid, filemon->fname1); filemon_output(filemon, filemon->msgbufr, len); filemon_drop(filemon); } } return (ret); } static void filemon_event_process_fork(void *arg __unused, struct proc *p1, struct proc *p2, int flags __unused) { size_t len; struct filemon *filemon; if ((filemon = filemon_proc_get(p1)) != NULL) { len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), "F %d %d\n", p1->p_pid, p2->p_pid); filemon_output(filemon, filemon->msgbufr, len); /* * filemon_untrack_processes() or * filemon_ioctl(FILEMON_SET_PID) may have changed the parent's * p_filemon while in filemon_proc_get() before acquiring the * filemon lock. Only inherit if the parent is still traced by * this filemon. */ if (p1->p_filemon == filemon) { PROC_LOCK(p2); /* * It may have been attached to already by a new * filemon. */ if (p2->p_filemon == NULL) { p2->p_filemon = filemon_acquire(filemon); ++filemon->proccnt; } PROC_UNLOCK(p2); } filemon_drop(filemon); } } static void filemon_wrapper_install(void) { #if defined(__LP64__) struct sysent *sv_table = elf64_freebsd_sysvec.sv_table; #else struct sysent *sv_table = elf32_freebsd_sysvec.sv_table; #endif sv_table[SYS_chdir].sy_call = (sy_call_t *) filemon_wrapper_chdir; sv_table[SYS_open].sy_call = (sy_call_t *) filemon_wrapper_open; sv_table[SYS_openat].sy_call = (sy_call_t *) filemon_wrapper_openat; sv_table[SYS_rename].sy_call = (sy_call_t *) filemon_wrapper_rename; sv_table[SYS_unlink].sy_call = (sy_call_t *) filemon_wrapper_unlink; sv_table[SYS_link].sy_call = (sy_call_t *) filemon_wrapper_link; sv_table[SYS_symlink].sy_call = (sy_call_t *) filemon_wrapper_symlink; sv_table[SYS_linkat].sy_call = (sy_call_t *) filemon_wrapper_linkat; #if defined(COMPAT_IA32) || defined(COMPAT_FREEBSD32) || defined(COMPAT_ARCH32) sv_table = ia32_freebsd_sysvec.sv_table; sv_table[FREEBSD32_SYS_chdir].sy_call = (sy_call_t *) filemon_wrapper_chdir; sv_table[FREEBSD32_SYS_open].sy_call = (sy_call_t *) filemon_wrapper_open; sv_table[FREEBSD32_SYS_openat].sy_call = (sy_call_t *) filemon_wrapper_openat; sv_table[FREEBSD32_SYS_rename].sy_call = (sy_call_t *) filemon_wrapper_rename; sv_table[FREEBSD32_SYS_unlink].sy_call = (sy_call_t *) filemon_wrapper_unlink; sv_table[FREEBSD32_SYS_link].sy_call = (sy_call_t *) filemon_wrapper_link; sv_table[FREEBSD32_SYS_symlink].sy_call = (sy_call_t *) filemon_wrapper_symlink; sv_table[FREEBSD32_SYS_linkat].sy_call = (sy_call_t *) filemon_wrapper_linkat; #endif /* COMPAT_ARCH32 */ filemon_exec_tag = EVENTHANDLER_REGISTER(process_exec, filemon_event_process_exec, NULL, EVENTHANDLER_PRI_LAST); filemon_exit_tag = EVENTHANDLER_REGISTER(process_exit, filemon_event_process_exit, NULL, EVENTHANDLER_PRI_LAST); filemon_fork_tag = EVENTHANDLER_REGISTER(process_fork, filemon_event_process_fork, NULL, EVENTHANDLER_PRI_LAST); } static void filemon_wrapper_deinstall(void) { #if defined(__LP64__) struct sysent *sv_table = elf64_freebsd_sysvec.sv_table; #else struct sysent *sv_table = elf32_freebsd_sysvec.sv_table; #endif sv_table[SYS_chdir].sy_call = (sy_call_t *)sys_chdir; sv_table[SYS_open].sy_call = (sy_call_t *)sys_open; sv_table[SYS_openat].sy_call = (sy_call_t *)sys_openat; sv_table[SYS_rename].sy_call = (sy_call_t *)sys_rename; sv_table[SYS_unlink].sy_call = (sy_call_t *)sys_unlink; sv_table[SYS_link].sy_call = (sy_call_t *)sys_link; sv_table[SYS_symlink].sy_call = (sy_call_t *)sys_symlink; sv_table[SYS_linkat].sy_call = (sy_call_t *)sys_linkat; #if defined(COMPAT_IA32) || defined(COMPAT_FREEBSD32) || defined(COMPAT_ARCH32) sv_table = ia32_freebsd_sysvec.sv_table; sv_table[FREEBSD32_SYS_chdir].sy_call = (sy_call_t *)sys_chdir; sv_table[FREEBSD32_SYS_open].sy_call = (sy_call_t *)sys_open; sv_table[FREEBSD32_SYS_openat].sy_call = (sy_call_t *)sys_openat; sv_table[FREEBSD32_SYS_rename].sy_call = (sy_call_t *)sys_rename; sv_table[FREEBSD32_SYS_unlink].sy_call = (sy_call_t *)sys_unlink; sv_table[FREEBSD32_SYS_link].sy_call = (sy_call_t *)sys_link; sv_table[FREEBSD32_SYS_symlink].sy_call = (sy_call_t *)sys_symlink; sv_table[FREEBSD32_SYS_linkat].sy_call = (sy_call_t *)sys_linkat; #endif /* COMPAT_ARCH32 */ EVENTHANDLER_DEREGISTER(process_exec, filemon_exec_tag); EVENTHANDLER_DEREGISTER(process_exit, filemon_exit_tag); EVENTHANDLER_DEREGISTER(process_fork, filemon_fork_tag); }