int main(int argc, char** argv) { int fan_fd, len; char buf[sizeof(struct fanotify_event_metadata)*1024]; struct fanotify_event_metadata *metadata; if (argc != 3) { printf("Usage: %s <root-dir> <procfs>\n", argv[0]); return 1; } procfs = argv[2]; fan_fd = fanotify_init(0, 0); if (fan_fd == -1) { perror("fanotify_init"); return 1; } fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_ACCESS, AT_FDCWD, argv[1]); if (fan_fd == -1) { perror("fanotify_mark"); return 1; } while (1) { len = read(fan_fd, buf, sizeof(buf)); metadata = (struct fanotify_event_metadata*)&buf; while (FAN_EVENT_OK(metadata, len)) { print_filename(metadata->fd); close(metadata->fd); metadata = FAN_EVENT_NEXT(metadata, len); } } }
void GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) { struct fanotify_event_metadata* fem = nullptr; char buf[4096]; struct statfs sfs; int32_t len, rc; do { len = read(aFd, buf, sizeof(buf)); } while(len == -1 && errno == EINTR); // Bail out if the file is busy. if (len < 0 && errno == ETXTBSY) { return; } // We should get an exact multiple of fanotify_event_metadata if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) { MOZ_CRASH("About to crash: fanotify_event_metadata read error."); } fem = reinterpret_cast<fanotify_event_metadata *>(buf); while (FAN_EVENT_OK(fem, len)) { rc = fstatfs(fem->fd, &sfs); if (rc < 0) { NS_WARNING("Unable to stat fan_notify fd"); } else { bool firstRun = mFreeSpace == UINT64_MAX; mFreeSpace = sfs.f_bavail * sfs.f_bsize; // We change from full <-> free depending on the free space and the // low and high thresholds. // Once we are in 'full' mode we send updates for all size changes with // a minimum of time between messages or when we cross a size change // threshold. if (firstRun) { mIsDiskFull = mFreeSpace <= mLowThreshold; // Always notify the current state at first run. NotifyUpdate(); } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) { mIsDiskFull = true; NotifyUpdate(); } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) { mIsDiskFull = false; NotifyUpdate(); } else if (mIsDiskFull) { if (mTimeout < TimeStamp::Now() - mLastTimestamp || mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) { NotifyUpdate(); } } } close(fem->fd); fem = FAN_EVENT_NEXT(fem, len); } }
/* Read all available fanotify events from the file descriptor 'fd' */ static void handle_events(int fd) { const struct fanotify_event_metadata *metadata; struct fanotify_event_metadata buf[200]; ssize_t len; char path[PATH_MAX]; ssize_t path_len; char procfd_path[PATH_MAX]; struct fanotify_response response; /* Loop while events can be read from fanotify file descriptor */ for(;;) { /* Read some events */ len = read(fd, (void *) &buf, sizeof(buf)); if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* Check if end of available data reached */ if (len <= 0) break; /* Point to the first event in the buffer */ metadata = buf; /* Loop over all events in the buffer */ while (FAN_EVENT_OK(metadata, len)) { /* Check that run-time and compile-time structures match */ if (metadata->vers != FANOTIFY_METADATA_VERSION) { fprintf(stderr, "Mismatch of fanotify metadata version.\n"); exit(EXIT_FAILURE); } /* metadata->fd contains either FAN_NOFD, indicating a queue overflow, or a file descriptor (a nonnegative integer). Here, we simply ignore queue overflow. */ if (metadata->fd >= 0) { /* Handle open permission event */ if (metadata->mask & FAN_OPEN_PERM) { printf("FAN_OPEN_PERM: "); /* Allow file to be opened */ response.fd = metadata->fd; response.response = FAN_ALLOW; write(fd, &response, sizeof(struct fanotify_response)); } /* Handle closing of writable file event */ if (metadata->mask & FAN_CLOSE_WRITE) printf("FAN_CLOSE_WRITE: "); /* Retrieve and print pathname of the accessed file */ snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", metadata->fd); path_len = readlink(procfd_path, path, sizeof(path) - 1); if (path_len == -1) { perror("readlink"); exit(EXIT_FAILURE); } path[path_len] = '\0'; printf("File %s\n", path); /* Close the file descriptor of the event */ close(metadata->fd); } /* Advance to next event */ metadata = FAN_EVENT_NEXT(metadata, len); } } }
/** * fanotify main loop * @todo simplify code (CCN is 12 already !) */ void fanotify_loop(main_struct_t *main_struct) { struct pollfd fds[FD_POLL_MAX]; struct signalfd_siginfo fdsi; char buffer[FANOTIFY_BUFFER_SIZE]; ssize_t length = 0; struct fanotify_event_metadata *fe_mdata = NULL; GSList *dir_list_utf8 = NULL; gint signal_fd = 0; gint fanotify_fd = 0; if (main_struct != NULL) { signal_fd = main_struct->signal_fd; fanotify_fd = main_struct->fanotify_fd; /* Setup polling */ fds[FD_POLL_SIGNAL].fd = signal_fd; fds[FD_POLL_SIGNAL].events = POLLIN; fds[FD_POLL_FANOTIFY].fd = fanotify_fd; fds[FD_POLL_FANOTIFY].events = POLLIN; dir_list_utf8 = transform_to_utf8_casefold(main_struct->opt->dirname_list); while (1) { /* Block until there is something to be read */ if (poll(fds, FD_POLL_MAX, -1) < 0) { print_error(__FILE__, __LINE__, _("Couldn't poll(): '%s'\n"), strerror(errno)); } /* Signal received ? */ if (fds[FD_POLL_SIGNAL].revents & POLLIN) { if (read(fds[FD_POLL_SIGNAL].fd, &fdsi, sizeof(fdsi)) != sizeof(fdsi)) { print_error(__FILE__, __LINE__, _("Couldn't read signal, wrong size read\n")); } /* Break loop if we got SIGINT or SIGTERM */ if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) { stop_fanotify(main_struct->opt, main_struct->fanotify_fd); free_list(dir_list_utf8); break; } print_error(__FILE__, __LINE__, _("Received unexpected signal\n")); } /* fanotify event received ? */ if (fds[FD_POLL_FANOTIFY].revents & POLLIN) { /* Read from the FD. It will read all events available up to * the given buffer size. */ if ((length = read(fds[FD_POLL_FANOTIFY].fd, buffer, FANOTIFY_BUFFER_SIZE)) > 0) { fe_mdata = (struct fanotify_event_metadata *) buffer; while (FAN_EVENT_OK(fe_mdata, length)) { event_process(main_struct, fe_mdata, dir_list_utf8); if (fe_mdata->fd > 0) { close(fe_mdata->fd); } fe_mdata = FAN_EVENT_NEXT(fe_mdata, length); } } } } } }
void *onas_fan_th(void *arg) { struct thrarg *tharg = (struct thrarg *) arg; sigset_t sigset; struct sigaction act; const struct optstruct *pt; short int scan; int sizelimit = 0, extinfo; STATBUF sb; uint64_t fan_mask = FAN_EVENT_ON_CHILD | FAN_CLOSE; fd_set rfds; char buf[4096]; ssize_t bread; struct fanotify_event_metadata *fmd; char fname[1024]; int ret, len; char err[128]; pthread_attr_t ddd_attr; struct ddd_thrarg *ddd_tharg = NULL; ddd_pid = 0; /* ignore all signals except SIGUSR1 */ sigfillset(&sigset); sigdelset(&sigset, SIGUSR1); /* The behavior of a process is undefined after it ignores a * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */ sigdelset(&sigset, SIGFPE); sigdelset(&sigset, SIGILL); sigdelset(&sigset, SIGSEGV); #ifdef SIGBUS sigdelset(&sigset, SIGBUS); #endif pthread_sigmask(SIG_SETMASK, &sigset, NULL); memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = onas_fan_exit; sigfillset(&(act.sa_mask)); sigaction(SIGUSR1, &act, NULL); sigaction(SIGSEGV, &act, NULL); /* Initialize fanotify */ onas_fan_fd = fanotify_init(FAN_CLASS_CONTENT | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS, O_LARGEFILE | O_RDONLY); if(onas_fan_fd < 0) { logg("!ScanOnAccess: fanotify_init failed: %s\n", cli_strerror(errno, err, sizeof(err))); if(errno == EPERM) logg("ScanOnAccess: clamd must be started by root\n"); return NULL; } if (!tharg) { logg("!Unable to start on-access scanner. Bad thread args.\n"); return NULL; } if (optget(tharg->opts, "OnAccessPrevention")->enabled && !optget(tharg->opts, "OnAccessMountPath")->enabled) { logg("ScanOnAccess: preventing access attempts on malicious files.\n"); fan_mask |= FAN_ACCESS_PERM | FAN_OPEN_PERM; } else { logg("ScanOnAccess: notifying only for access attempts.\n"); fan_mask |= FAN_ACCESS | FAN_OPEN; } if ((pt = optget(tharg->opts, "OnAccessMountPath"))->enabled) { while(pt) { if(fanotify_mark(onas_fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, fan_mask, onas_fan_fd, pt->strarg) != 0) { logg("!ScanOnAccess: Can't include mountpoint '%s'\n", pt->strarg); return NULL; } else logg("ScanOnAccess: Protecting '%s' and rest of mount.\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } else if (!optget(tharg->opts, "OnAccessDisableDDD")->enabled) { do { if(pthread_attr_init(&ddd_attr)) break; pthread_attr_setdetachstate(&ddd_attr, PTHREAD_CREATE_JOINABLE); if(!(ddd_tharg = (struct ddd_thrarg *) malloc(sizeof(struct ddd_thrarg)))) break; ddd_tharg->fan_fd = onas_fan_fd; ddd_tharg->fan_mask = fan_mask; ddd_tharg->opts = tharg->opts; ddd_tharg->engine = tharg->engine; ddd_tharg->options = tharg->options; if(!pthread_create(&ddd_pid, &ddd_attr, onas_ddd_th, ddd_tharg)) break; free(ddd_tharg); ddd_tharg=NULL; } while(0); if (!ddd_tharg) logg("!Unable to start dynamic directory determination.\n"); } else { if((pt = optget(tharg->opts, "OnAccessIncludePath"))->enabled) { while(pt) { if(fanotify_mark(onas_fan_fd, FAN_MARK_ADD, fan_mask, onas_fan_fd, pt->strarg) != 0) { logg("!ScanOnAccess: Can't include path '%s'\n", pt->strarg); return NULL; } else logg("ScanOnAccess: Protecting directory '%s'\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } else { logg("!ScanOnAccess: Please specify at least one path with OnAccessIncludePath\n"); return NULL; } } /* Load other options. */ sizelimit = optget(tharg->opts, "OnAccessMaxFileSize")->numarg; if(sizelimit) logg("ScanOnAccess: Max file size limited to %d bytes\n", sizelimit); else logg("ScanOnAccess: File size limit disabled\n"); extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled; FD_ZERO(&rfds); FD_SET(onas_fan_fd, &rfds); do { if (reload) sleep(1); ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL); } while((ret == -1 && errno == EINTR) || reload); time_t start = time(NULL) - 30; while(((bread = read(onas_fan_fd, buf, sizeof(buf))) > 0) || errno == EOVERFLOW) { if (errno == EOVERFLOW) { if (time(NULL) - start >= 30) { logg("!ScanOnAccess: Internal error (failed to read data) ... %s\n", strerror(errno)); logg("!ScanOnAccess: File too large for fanotify ... recovering and continuing scans...\n"); start = time(NULL); } errno = 0; continue; } fmd = (struct fanotify_event_metadata *) buf; while(FAN_EVENT_OK(fmd, bread)) { scan = 1; if(fmd->fd >= 0) { sprintf(fname, "/proc/self/fd/%d", fmd->fd); len = readlink(fname, fname, sizeof(fname) - 1); if(len == -1) { close(fmd->fd); logg("!ScanOnAccess: Internal error (readlink() failed)\n"); return NULL; } fname[len] = 0; if(onas_fan_checkowner(fmd->pid, tharg->opts)) { scan = 0; logg("*ScanOnAccess: %s skipped (excluded UID)\n", fname); } if(sizelimit) { if(FSTAT(fmd->fd, &sb) != 0 || sb.st_size > sizelimit) { scan = 0; /* logg("*ScanOnAccess: %s skipped (size > %d)\n", fname, sizelimit); */ } } if(onas_fan_scanfile(onas_fan_fd, fname, fmd, scan, extinfo, tharg) == -1) { close(fmd->fd); return NULL; } if(close(fmd->fd) == -1) { printf("!ScanOnAccess: Internal error (close(%d) failed)\n", fmd->fd); close(fmd->fd); return NULL; } } fmd = FAN_EVENT_NEXT(fmd, bread); } do { if (reload) sleep(1); ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL); } while((ret == -1 && errno == EINTR) || reload); } if(bread < 0) logg("!ScanOnAccess: Internal error (failed to read data) ... %s\n", strerror(errno)); return NULL; }
void ScannerThread::handleFanotifyEvent() { char buf[(FAN_EVENT_METADATA_LEN / 2) * 3]; ssize_t len; int fanotifyfd = m_fanotifyfd; setState(ScannerThread::BEFORE_READ); len = ::read(fanotifyfd, buf, sizeof(buf)); setState(ScannerThread::AFTER_READ); if (len <= 0) { // nothing actually there - maybe another thread got it if (errno != EINTR && errno != EAGAIN) { PRINT(m_thread<<" no event or error: " << len << " (" << errno <<" "<<strerror(errno)<< ")"); } return; } struct fanotify_event_metadata* metadata = reinterpret_cast<struct fanotify_event_metadata*>(buf); for (; FAN_EVENT_OK(metadata, len); metadata = FAN_EVENT_NEXT(metadata, len)) { if (metadata->vers < 2) { PRINT("fanotify kernel version too old"); throw "FIXME"; // TODO: Throw proper exception } if (metadata->vers != FANOTIFY_METADATA_VERSION) { // TODO? PRINT("fanotify wrong protocol version " << metadata->vers); throw "FIXME2"; // TODO: Throw proper exception } if ((metadata->mask & FAN_ALL_PERM_EVENTS) == 0) { close(metadata->fd); continue; } std::string path = getPath(metadata->fd); //~ { //~ struct stat fstatbuf; //~ int ret = ::fstat(metadata->fd, &fstatbuf); //~ if (ret < 0) //~ { //~ PRINT("Failed to fstat for "<<metadata->pid <<" "<<path); //~ } //~ } { struct fanotify_response response_struct; ssize_t ret; response_struct.fd = metadata->fd; response_struct.response = FAN_ALLOW; PRINT(m_thread<< " Responding to fanotify event for "<<metadata->pid << " "<<path); setState(ScannerThread::BEFORE_WRITE); ret = ::write(fanotifyfd, &response_struct, sizeof(response_struct)); setState(ScannerThread::AFTER_WRITE); if (ret != sizeof(response_struct)) { PRINT(m_thread<<" response error " << ret << " (" << errno <<" "<<strerror(errno)<< ")"); } } { unsigned int flags = FAN_MARK_ADD | FAN_MARK_IGNORED_MASK; int ret = fanotify_mark(fanotifyfd,flags, FAN_OPEN_PERM, metadata->fd, NULL); if (ret < 0) { PRINT(m_thread<<" adding cache mark failed: " << ret); } } if (metadata->fd >= 0) { ::close(metadata->fd); } } }
void *fan_th(void *arg) { struct thrarg *tharg = (struct thrarg *) arg; sigset_t sigset; struct sigaction act; const struct optstruct *pt; short int scan; int sizelimit = 0, extinfo; STATBUF sb; uint64_t fan_mask = FAN_ACCESS | FAN_EVENT_ON_CHILD; int fan_fd; fd_set rfds; char buf[4096]; ssize_t bread; struct fanotify_event_metadata *fmd; char fname[1024]; int ret, len; char err[128]; /* ignore all signals except SIGUSR1 */ sigfillset(&sigset); sigdelset(&sigset, SIGUSR1); /* The behavior of a process is undefined after it ignores a * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */ sigdelset(&sigset, SIGFPE); sigdelset(&sigset, SIGILL); sigdelset(&sigset, SIGSEGV); #ifdef SIGBUS sigdelset(&sigset, SIGBUS); #endif pthread_sigmask(SIG_SETMASK, &sigset, NULL); memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = fan_exit; sigfillset(&(act.sa_mask)); sigaction(SIGUSR1, &act, NULL); sigaction(SIGSEGV, &act, NULL); fan_fd = fanotify_init(0, O_RDONLY); if(fan_fd < 0) { logg("!ScanOnAccess: fanotify_init failed: %s\n", cli_strerror(errno, err, sizeof(err))); if(errno == EPERM) logg("ScanOnAccess: clamd must be started by root\n"); return NULL; } if((pt = optget(tharg->opts, "OnAccessIncludePath"))->enabled) { while(pt) { if(fanotify_mark(fan_fd, FAN_MARK_ADD, fan_mask, fan_fd, pt->strarg) != 0) { logg("!ScanOnAccess: Can't include path '%s'\n", pt->strarg); return NULL; } else logg("ScanOnAccess: Protecting directory '%s'\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } else { logg("!ScanOnAccess: Please specify at least one path with OnAccessIncludePath\n"); return NULL; } if((pt = optget(tharg->opts, "OnAccessExcludePath"))->enabled) { while(pt) { if(fanotify_mark(fan_fd, FAN_MARK_REMOVE, fan_mask, fan_fd, pt->strarg) != 0) { logg("!ScanOnAccess: Can't exclude path %s\n", pt->strarg); return NULL; } else logg("ScanOnAccess: Excluded path %s\n", pt->strarg); pt = (struct optstruct *) pt->nextarg; } } sizelimit = optget(tharg->opts, "OnAccessMaxFileSize")->numarg; if(sizelimit) logg("ScanOnAccess: Max file size limited to %d bytes\n", sizelimit); else logg("ScanOnAccess: File size limit disabled\n"); extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled; FD_ZERO(&rfds); FD_SET(fan_fd, &rfds); do { ret = select(fan_fd + 1, &rfds, NULL, NULL, NULL); } while(ret == -1 && errno == EINTR); while((bread = read(fan_fd, buf, sizeof(buf))) > 0) { fmd = (struct fanotify_event_metadata *) buf; while(FAN_EVENT_OK(fmd, bread)) { scan = 1; if(fmd->fd >= 0) { sprintf(fname, "/proc/self/fd/%d", fmd->fd); len = readlink(fname, fname, sizeof(fname) - 1); if(len == -1) { close(fmd->fd); logg("!ScanOnAccess: Internal error (readlink() failed)\n"); return NULL; } fname[len] = 0; if(fan_checkowner(fmd->pid, tharg->opts)) { scan = 0; logg("*ScanOnAccess: %s skipped (excluded UID)\n", fname); } if(sizelimit) { if(FSTAT(fmd->fd, &sb) != 0 || sb.st_size > sizelimit) { scan = 0; /* logg("*ScanOnAccess: %s skipped (size > %d)\n", fname, sizelimit); */ } } if(fan_scanfile(fan_fd, fname, fmd, scan, extinfo, tharg) == -1) { close(fmd->fd); return NULL; } if(close(fmd->fd) == -1) { printf("!ScanOnAccess: Internal error (close(%d) failed)\n", fmd->fd); close(fmd->fd); return NULL; } } fmd = FAN_EVENT_NEXT(fmd, bread); } do { ret = select(fan_fd + 1, &rfds, NULL, NULL, NULL); } while(ret == -1 && errno == EINTR); } if(bread < 0) logg("!ScanOnAccess: Internal error (failed to read data)\n"); return NULL; }
/* ファイルディスクリプター 'fd' から読み出しできる全 fanotify イベントを読み出す */ static void handle_events(int fd) { const struct fanotify_event_metadata *metadata; struct fanotify_event_metadata buf[200]; ssize_t len; char path[PATH_MAX]; ssize_t path_len; char procfd_path[PATH_MAX]; struct fanotify_response response; /* fanotify ファイルディスクリプターからイベントが読み出せる間はループする */ for(;;) { /* イベントを読み出す */ len = read(fd, (void *) &buf, sizeof(buf)); if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* 読み出せるデータの最後に達しているかチェックする */ if (len <= 0) break; /* バッファーの最初のイベントを参照する */ metadata = buf; /* バッファー内の全イベントを処理する */ while (FAN_EVENT_OK(metadata, len)) { /* 実行時とコンパイル時の構造体が一致するか確認する */ if (metadata->vers != FANOTIFY_METADATA_VERSION) { fprintf(stderr, "Mismatch of fanotify metadata version.\n"); exit(EXIT_FAILURE); } /* metadata->fd には、キューのオーバーフローを示す FAN_NOFD か、 ファイルディスクリプター (負でない整数) のいずれかが入っている。 ここではキューのオーバーフローは無視している。 */ if (metadata->fd >= 0) { /* オープン許可イベントを処理する */ if (metadata->mask & FAN_OPEN_PERM) { printf("FAN_OPEN_PERM: "); /* ファイルのオープンを許可する */ response.fd = metadata->fd; response.response = FAN_ALLOW; write(fd, &response, sizeof(struct fanotify_response)); } /* 書き込み可能ファイルのクローズイベントを処理する */ if (metadata->mask & FAN_CLOSE_WRITE) printf("FAN_CLOSE_WRITE: "); /* アクセスされたファイルのパス名を取得し表示する */ snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", metadata->fd); path_len = readlink(procfd_path, path, sizeof(path) - 1); if (path_len == -1) { perror("readlink"); exit(EXIT_FAILURE); } path[path_len] = '\0'; printf("File %s\n", path); /* イベントのファイルディスクリプターをクローズする */ close(metadata->fd); } /* 次のイベントに進む */ metadata = FAN_EVENT_NEXT(metadata, len); } } }