filemon.c revision 302072
1/*- 2 * Copyright (c) 2011, David E. O'Brien. 3 * Copyright (c) 2009-2011, Juniper Networks, Inc. 4 * Copyright (c) 2015-2016, EMC Corp. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY JUNIPER NETWORKS AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL JUNIPER NETWORKS OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__FBSDID("$FreeBSD: stable/10/sys/dev/filemon/filemon.c 302072 2016-06-21 20:32:34Z bdrewery $"); 31 32#include "opt_compat.h" 33 34#include <sys/param.h> 35#include <sys/file.h> 36#include <sys/systm.h> 37#include <sys/buf.h> 38#include <sys/capsicum.h> 39#include <sys/condvar.h> 40#include <sys/conf.h> 41#include <sys/fcntl.h> 42#include <sys/ioccom.h> 43#include <sys/kernel.h> 44#include <sys/lock.h> 45#include <sys/malloc.h> 46#include <sys/module.h> 47#include <sys/poll.h> 48#include <sys/proc.h> 49#include <sys/sx.h> 50#include <sys/syscall.h> 51#include <sys/sysent.h> 52#include <sys/sysproto.h> 53#include <sys/uio.h> 54 55#include "filemon.h" 56 57#if defined(COMPAT_FREEBSD32) 58#include <compat/freebsd32/freebsd32_syscall.h> 59#include <compat/freebsd32/freebsd32_proto.h> 60#include <compat/freebsd32/freebsd32_util.h> 61#endif 62 63static d_close_t filemon_close; 64static d_ioctl_t filemon_ioctl; 65static d_open_t filemon_open; 66 67static struct cdevsw filemon_cdevsw = { 68 .d_version = D_VERSION, 69 .d_close = filemon_close, 70 .d_ioctl = filemon_ioctl, 71 .d_open = filemon_open, 72 .d_name = "filemon", 73}; 74 75MALLOC_DECLARE(M_FILEMON); 76MALLOC_DEFINE(M_FILEMON, "filemon", "File access monitor"); 77 78/* 79 * The filemon->lock protects several things currently: 80 * - fname1/fname2/msgbufr are pre-allocated and used per syscall 81 * for logging and copyins rather than stack variables. 82 * - Serializing the filemon's log output. 83 * - Preventing inheritance or removal of the filemon into proc.p_filemon. 84 */ 85struct filemon { 86 struct sx lock; /* Lock for this filemon. */ 87 struct file *fp; /* Output file pointer. */ 88 char fname1[MAXPATHLEN]; /* Temporary filename buffer. */ 89 char fname2[MAXPATHLEN]; /* Temporary filename buffer. */ 90 char msgbufr[1024]; /* Output message buffer. */ 91 int error; /* Log write error, returned on close(2). */ 92 u_int refcnt; /* Pointer reference count. */ 93 u_int proccnt; /* Process count. */ 94}; 95 96static struct cdev *filemon_dev; 97static void filemon_output(struct filemon *filemon, char *msg, size_t len); 98 99static __inline struct filemon * 100filemon_acquire(struct filemon *filemon) 101{ 102 103 if (filemon != NULL) 104 refcount_acquire(&filemon->refcnt); 105 return (filemon); 106} 107 108/* 109 * Release a reference and free on the last one. 110 */ 111static void 112filemon_release(struct filemon *filemon) 113{ 114 115 if (refcount_release(&filemon->refcnt) == 0) 116 return; 117 /* 118 * There are valid cases of releasing while locked, such as in 119 * filemon_untrack_processes, but none which are done where there 120 * is not at least 1 reference remaining. 121 */ 122 sx_assert(&filemon->lock, SA_UNLOCKED); 123 124 sx_destroy(&filemon->lock); 125 free(filemon, M_FILEMON); 126} 127 128/* 129 * Acquire the proc's p_filemon reference and lock the filemon. 130 * The proc's p_filemon may not match this filemon on return. 131 */ 132static struct filemon * 133filemon_proc_get(struct proc *p) 134{ 135 struct filemon *filemon; 136 137 PROC_LOCK(p); 138 filemon = filemon_acquire(p->p_filemon); 139 PROC_UNLOCK(p); 140 141 if (filemon == NULL) 142 return (NULL); 143 /* 144 * The p->p_filemon may have changed by now. That case is handled 145 * by the exit and fork hooks and filemon_attach_proc specially. 146 */ 147 sx_xlock(&filemon->lock); 148 return (filemon); 149} 150 151/* Remove and release the filemon on the given process. */ 152static void 153filemon_proc_drop(struct proc *p) 154{ 155 struct filemon *filemon; 156 157 KASSERT(p->p_filemon != NULL, ("%s: proc %p NULL p_filemon", 158 __func__, p)); 159 sx_assert(&p->p_filemon->lock, SA_XLOCKED); 160 PROC_LOCK(p); 161 filemon = p->p_filemon; 162 p->p_filemon = NULL; 163 --filemon->proccnt; 164 PROC_UNLOCK(p); 165 /* 166 * This should not be the last reference yet. filemon_release() 167 * cannot be called with filemon locked, which the caller expects 168 * will stay locked. 169 */ 170 KASSERT(filemon->refcnt > 1, ("%s: proc %p dropping filemon %p " 171 "with last reference", __func__, p, filemon)); 172 filemon_release(filemon); 173} 174 175/* Unlock and release the filemon. */ 176static __inline void 177filemon_drop(struct filemon *filemon) 178{ 179 180 sx_xunlock(&filemon->lock); 181 filemon_release(filemon); 182} 183 184#include "filemon_wrapper.c" 185 186static void 187filemon_write_header(struct filemon *filemon) 188{ 189 int len; 190 struct timeval now; 191 192 getmicrotime(&now); 193 194 len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr), 195 "# filemon version %d\n# Target pid %d\n# Start %ju.%06ju\nV %d\n", 196 FILEMON_VERSION, curproc->p_pid, (uintmax_t)now.tv_sec, 197 (uintmax_t)now.tv_usec, FILEMON_VERSION); 198 199 filemon_output(filemon, filemon->msgbufr, len); 200} 201 202/* 203 * Invalidate the passed filemon in all processes. 204 */ 205static void 206filemon_untrack_processes(struct filemon *filemon) 207{ 208 struct proc *p; 209 210 sx_assert(&filemon->lock, SA_XLOCKED); 211 212 /* Avoid allproc loop if there is no need. */ 213 if (filemon->proccnt == 0) 214 return; 215 216 /* 217 * Processes in this list won't go away while here since 218 * filemon_event_process_exit() will lock on filemon->lock 219 * which we hold. 220 */ 221 sx_slock(&allproc_lock); 222 FOREACH_PROC_IN_SYSTEM(p) { 223 /* 224 * No PROC_LOCK is needed to compare here since it is 225 * guaranteed to not change since we have its filemon 226 * locked. Everything that changes this p_filemon will 227 * be locked on it. 228 */ 229 if (p->p_filemon == filemon) 230 filemon_proc_drop(p); 231 } 232 sx_sunlock(&allproc_lock); 233 234 /* 235 * It's possible some references were acquired but will be 236 * dropped shortly as they are restricted from being 237 * inherited. There is at least the reference in cdevpriv remaining. 238 */ 239 KASSERT(filemon->refcnt > 0, ("%s: filemon %p should have " 240 "references still.", __func__, filemon)); 241 KASSERT(filemon->proccnt == 0, ("%s: filemon %p should not have " 242 "attached procs still.", __func__, filemon)); 243} 244 245/* 246 * Close out the log. 247 */ 248static void 249filemon_close_log(struct filemon *filemon) 250{ 251 struct file *fp; 252 struct timeval now; 253 size_t len; 254 255 sx_assert(&filemon->lock, SA_XLOCKED); 256 if (filemon->fp == NULL) 257 return; 258 259 getmicrotime(&now); 260 261 len = snprintf(filemon->msgbufr, 262 sizeof(filemon->msgbufr), 263 "# Stop %ju.%06ju\n# Bye bye\n", 264 (uintmax_t)now.tv_sec, (uintmax_t)now.tv_usec); 265 266 filemon_output(filemon, filemon->msgbufr, len); 267 fp = filemon->fp; 268 filemon->fp = NULL; 269 270 sx_xunlock(&filemon->lock); 271 fdrop(fp, curthread); 272 sx_xlock(&filemon->lock); 273} 274 275/* 276 * The devfs file is being closed. Untrace all processes. It is possible 277 * filemon_close/close(2) was not called. 278 */ 279static void 280filemon_dtr(void *data) 281{ 282 struct filemon *filemon = data; 283 284 if (filemon == NULL) 285 return; 286 287 sx_xlock(&filemon->lock); 288 /* 289 * Detach the filemon. It cannot be inherited after this. 290 */ 291 filemon_untrack_processes(filemon); 292 filemon_close_log(filemon); 293 filemon_drop(filemon); 294} 295 296/* Attach the filemon to the process. */ 297static int 298filemon_attach_proc(struct filemon *filemon, struct proc *p) 299{ 300 struct filemon *filemon2; 301 302 sx_assert(&filemon->lock, SA_XLOCKED); 303 PROC_LOCK_ASSERT(p, MA_OWNED); 304 KASSERT((p->p_flag & P_WEXIT) == 0, 305 ("%s: filemon %p attaching to exiting process %p", 306 __func__, filemon, p)); 307 308 if (p->p_filemon == filemon) 309 return (0); 310 /* 311 * Don't allow truncating other process traces. It is 312 * not really intended to trace procs other than curproc 313 * anyhow. 314 */ 315 if (p->p_filemon != NULL && p != curproc) 316 return (EBUSY); 317 /* 318 * Historic behavior of filemon has been to let a child initiate 319 * tracing on itself and cease existing tracing. Bmake 320 * .META + .MAKE relies on this. It is only relevant for attaching to 321 * curproc. 322 */ 323 while (p->p_filemon != NULL) { 324 PROC_UNLOCK(p); 325 sx_xunlock(&filemon->lock); 326 while ((filemon2 = filemon_proc_get(p)) != NULL) { 327 /* It may have changed. */ 328 if (p->p_filemon == filemon2) 329 filemon_proc_drop(p); 330 filemon_drop(filemon2); 331 } 332 sx_xlock(&filemon->lock); 333 PROC_LOCK(p); 334 /* 335 * It may have been attached to, though unlikely. 336 * Try again if needed. 337 */ 338 } 339 340 KASSERT(p->p_filemon == NULL, 341 ("%s: proc %p didn't detach filemon %p", __func__, p, 342 p->p_filemon)); 343 p->p_filemon = filemon_acquire(filemon); 344 ++filemon->proccnt; 345 346 return (0); 347} 348 349static int 350filemon_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag __unused, 351 struct thread *td) 352{ 353 int error = 0; 354 struct filemon *filemon; 355 struct proc *p; 356 cap_rights_t rights; 357 358 if ((error = devfs_get_cdevpriv((void **) &filemon)) != 0) 359 return (error); 360 361 sx_xlock(&filemon->lock); 362 363 switch (cmd) { 364 /* Set the output file descriptor. */ 365 case FILEMON_SET_FD: 366 if (filemon->fp != NULL) { 367 error = EEXIST; 368 break; 369 } 370 371 error = fget_write(td, *(int *)data, 372 cap_rights_init(&rights, CAP_PWRITE), 373 &filemon->fp); 374 if (error == 0) 375 /* Write the file header. */ 376 filemon_write_header(filemon); 377 break; 378 379 /* Set the monitored process ID. */ 380 case FILEMON_SET_PID: 381 /* Invalidate any existing processes already set. */ 382 filemon_untrack_processes(filemon); 383 384 error = pget(*((pid_t *)data), PGET_CANDEBUG | PGET_NOTWEXIT, 385 &p); 386 if (error == 0) { 387 KASSERT(p->p_filemon != filemon, 388 ("%s: proc %p didn't untrack filemon %p", 389 __func__, p, filemon)); 390 error = filemon_attach_proc(filemon, p); 391 PROC_UNLOCK(p); 392 } 393 break; 394 395 default: 396 error = EINVAL; 397 break; 398 } 399 400 sx_xunlock(&filemon->lock); 401 return (error); 402} 403 404static int 405filemon_open(struct cdev *dev, int oflags __unused, int devtype __unused, 406 struct thread *td __unused) 407{ 408 int error; 409 struct filemon *filemon; 410 411 filemon = malloc(sizeof(*filemon), M_FILEMON, 412 M_WAITOK | M_ZERO); 413 sx_init(&filemon->lock, "filemon"); 414 refcount_init(&filemon->refcnt, 1); 415 416 error = devfs_set_cdevpriv(filemon, filemon_dtr); 417 if (error != 0) 418 filemon_release(filemon); 419 420 return (error); 421} 422 423/* Called on close of last devfs file handle, before filemon_dtr(). */ 424static int 425filemon_close(struct cdev *dev __unused, int flag __unused, int fmt __unused, 426 struct thread *td __unused) 427{ 428 struct filemon *filemon; 429 int error; 430 431 if ((error = devfs_get_cdevpriv((void **) &filemon)) != 0) 432 return (error); 433 434 sx_xlock(&filemon->lock); 435 filemon_close_log(filemon); 436 error = filemon->error; 437 sx_xunlock(&filemon->lock); 438 /* 439 * Processes are still being traced but won't log anything 440 * now. After this call returns filemon_dtr() is called which 441 * will detach processes. 442 */ 443 444 return (error); 445} 446 447static void 448filemon_load(void *dummy __unused) 449{ 450 451 /* Install the syscall wrappers. */ 452 filemon_wrapper_install(); 453 454 filemon_dev = make_dev(&filemon_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, 455 "filemon"); 456} 457 458static int 459filemon_unload(void) 460{ 461 462 destroy_dev(filemon_dev); 463 filemon_wrapper_deinstall(); 464 465 return (0); 466} 467 468static int 469filemon_modevent(module_t mod __unused, int type, void *data) 470{ 471 int error = 0; 472 473 switch (type) { 474 case MOD_LOAD: 475 filemon_load(data); 476 break; 477 478 case MOD_UNLOAD: 479 error = filemon_unload(); 480 break; 481 482 case MOD_QUIESCE: 483 /* 484 * The wrapper implementation is unsafe for reliable unload. 485 * Require forcing an unload. 486 */ 487 error = EBUSY; 488 break; 489 490 case MOD_SHUTDOWN: 491 break; 492 493 default: 494 error = EOPNOTSUPP; 495 break; 496 497 } 498 499 return (error); 500} 501 502DEV_MODULE(filemon, filemon_modevent, NULL); 503MODULE_VERSION(filemon, 1); 504