text: /*
   *  Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
   *
   *  Authors: Mickey Sola
   *
   *  This program is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License version 2 as
   *  published by the Free Software Foundation.
   *
   *  This program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with this program; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   *  MA 02110-1301, USA.
   */
  
  #if HAVE_CONFIG_H
  #include "clamav-config.h"
  #endif
  
  #if defined(FANOTIFY)
  
  #include <stdio.h>
  #include <unistd.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <pthread.h>
  #include <string.h>
  #include <errno.h>
  #include <time.h>
  
  #include <sys/fanotify.h>
  
  #include "libclamav/clamav.h"
  #include "libclamav/scanners.h"
  
  #include "shared/optparser.h"
  #include "shared/output.h"
  
  #include "clamd/server.h"
  
  #include "../inotif/hash.h"
  #include "../inotif/inotif.h"
  
  #include "../client/client.h"
  
  #include "../scan/thread.h"
  #include "../scan/queue.h"
  
  #include "../misc/utils.h"
  
  #include "fanotif.h"
  
  extern pthread_t ddd_pid;
  extern pthread_t scan_queue_pid;
  static int onas_fan_fd;
  
  cl_error_t onas_setup_fanotif(struct onas_context **ctx)
  {
  
      const struct optstruct *pt;
      uint64_t fan_mask      = FAN_EVENT_ON_CHILD;
  
      ddd_pid = 0;
  
      if (!ctx || !*ctx) {
          logg("!ClamFanotif: unable to start clamonacc. (bad context)\n");
          return CL_EARG;
      }
  
      onas_fan_fd      = (*ctx)->fan_fd;
      (*ctx)->fan_mask = fan_mask;
  
      if (optget((*ctx)->clamdopts, "OnAccessPrevention")->enabled && !optget((*ctx)->clamdopts, "OnAccessMountPath")->enabled) {
          logg("*ClamFanotif: kernel-level blocking feature enabled ... preventing malicious files access attempts\n");
          (*ctx)->fan_mask |= FAN_ACCESS_PERM | FAN_OPEN_PERM;
      } else {
          logg("*ClamFanotif: kernel-level blocking feature disabled ...\n");
          if (optget((*ctx)->clamdopts, "OnAccessPrevention")->enabled && optget((*ctx)->clamdopts, "OnAccessMountPath")->enabled) {
              logg("*ClamFanotif: feature not available when watching mounts ... \n");
          }
          (*ctx)->fan_mask |= FAN_ACCESS | FAN_OPEN;
      }
  
      if ((pt = optget((*ctx)->clamdopts, "OnAccessMountPath"))->enabled) {
          while (pt) {
              if (fanotify_mark(onas_fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, (*ctx)->fan_mask, (*ctx)->fan_fd, pt->strarg) != 0) {
                  logg("!ClamFanotif: can't include mountpoint '%s'\n", pt->strarg);
                  return CL_EARG;
              } else {
                  logg("*ClamFanotif: recursively watching the mount point '%s'\n", pt->strarg);
              }
              pt = (struct optstruct *)pt->nextarg;
          }
  
      } else if (!optget((*ctx)->clamdopts, "OnAccessDisableDDD")->enabled) {
          (*ctx)->ddd_enabled = 1;
      } else {
          if ((pt = optget((*ctx)->clamdopts, "OnAccessIncludePath"))->enabled) {
              while (pt) {
                  if (fanotify_mark(onas_fan_fd, FAN_MARK_ADD, (*ctx)->fan_mask, (*ctx)->fan_fd, pt->strarg) != 0) {
                      logg("!ClamFanotif: can't include path '%s'\n", pt->strarg);
                      return CL_EARG;
                  } else {
                      logg("*ClamFanotif: watching directory '%s' (non-recursively)\n", pt->strarg);
                  }
                  pt = (struct optstruct *)pt->nextarg;
              }
          } else {
              logg("!ClamFanotif: please specify at least one path with OnAccessIncludePath\n");
              return CL_EARG;
          }
      }
  
      /* Load other options. */
      (*ctx)->sizelimit = optget((*ctx)->clamdopts, "OnAccessMaxFileSize")->numarg;
      if ((*ctx)->sizelimit) {
          logg("*ClamFanotif: max file size limited to %lu bytes\n", (*ctx)->sizelimit);
      } else {
          logg("*ClamFanotif: file size limit disabled\n");
      }
  
      return CL_SUCCESS;
  }
  
  int onas_fan_eloop(struct onas_context **ctx)
  {
      int ret     = 0;
      int err_cnt = 0;
      short int scan;
      fd_set rfds;
      char buf[4096];
      ssize_t bread;
      struct fanotify_event_metadata *fmd;
      char fname[1024];
      int len, check;
  
      FD_ZERO(&rfds);
      FD_SET((*ctx)->fan_fd, &rfds);
  
      logg("*ClamFanotif: starting fanotify event loop with process id (%d) ... \n", getpid());
      do {
          ret = select((*ctx)->fan_fd + 1, &rfds, NULL, NULL, NULL);
      } while ((ret == -1 && errno == EINTR));
  
      time_t start = time(NULL) - 30;
      while (((bread = read((*ctx)->fan_fd, buf, sizeof(buf))) > 0) || (errno == EOVERFLOW || errno == EMFILE || errno == EACCES)) {
          switch (errno) {
              case EOVERFLOW:
                  if (time(NULL) - start >= 30) {
                      logg("*ClamFanotif: internal error (failed to read data) ... %s\n", strerror(errno));
                      logg("*ClamFanotif: file too large for fanotify ... recovering and continuing scans...\n");
                      start = time(NULL);
                  }
  
                  errno = 0;
                  continue;
              case EACCES:
                  logg("*ClamFanotif: internal error (failed to read data) ... %s\n", strerror(errno));
                  logg("*ClamFanotif: check your SELinux audit logs and consider adding an exception \
  						... recovering and continuing scans...\n");
  
                  errno = 0;
                  continue;
              case EMFILE:
                  logg("*ClamFanotif: internal error (failed to read data) ... %s\n", strerror(errno));
                  logg("*ClamFanotif: waiting for consumer thread to catch up then retrying ...\n");
                  sleep(3);
  
                  errno = 0;
                  continue;
              default:
                  break;
          }
  
          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);
                  errno = 0;
                  len   = readlink(fname, fname, sizeof(fname) - 1);
                  if (len == -1) {
                      close(fmd->fd);
                      logg("!ClamFanotif: internal error (readlink() failed), %d, %s\n", fmd->fd, strerror(errno));
                      if (errno == EBADF) {
                          logg("ClamWorker: fd already closed ... recovering ...\n");
                          continue;
                      } else {
                          return 2;
                      }
                  }
                  fname[len] = '\0';
  
                  if ((check = onas_fan_checkowner(fmd->pid, (*ctx)->clamdopts))) {
                      scan = 0;
                      if (check != CHK_SELF) {
                          logg("*ClamFanotif: %s skipped (excluded UID)\n", fname);
                      }
                  }
  
                  if (scan) {
                      struct onas_scan_event *event_data;
  
                      event_data = cli_calloc(1, sizeof(struct onas_scan_event));
                      if (NULL == event_data) {
                          logg("!ClamFanotif: could not allocate memory for event data struct\n");
                          return 2;
                      }
  
                      /* general mapping */
                      onas_map_context_info_to_event_data(*ctx, &event_data);
                      scan ? event_data->bool_opts |= ONAS_SCTH_B_SCAN : scan;
  
                      /* fanotify specific stuffs */
                      event_data->bool_opts |= ONAS_SCTH_B_FANOTIFY;
                      event_data->fmd = cli_malloc(sizeof(struct fanotify_event_metadata));
                      if (NULL == event_data->fmd) {
                          logg("!ClamFanotif: could not allocate memory for event data struct fmd\n");
                          return 2;
                      }
                      memcpy(event_data->fmd, fmd, sizeof(struct fanotify_event_metadata));
                      event_data->pathname = cli_strdup(fname);
  
                      logg("*ClamFanotif: attempting to feed consumer queue\n");
                      /* feed consumer queue */
                      if (CL_SUCCESS != onas_queue_event(event_data)) {
                          close(fmd->fd);
                          logg("!ClamFanotif: error occurred while feeding consumer queue ... \n");
                          if ((*ctx)->retry_on_error) {
                              err_cnt++;
                              if (err_cnt < (*ctx)->retry_attempts) {
                                  logg("ClamFanotif: ... recovering ...\n");
                                  continue;
                              }
                          }
                          return 2;
                      }
                  } else {
                      if (fmd->mask & FAN_ALL_PERM_EVENTS) {
                          struct fanotify_response res;
  
                          res.fd       = fmd->fd;
                          res.response = FAN_ALLOW;
  
                          if (-1 == write((*ctx)->fan_fd, &res, sizeof(res))) {
                              logg("!ClamFanotif: error occurred while excluding event\n");
                              return 2;
                          }
                      }
  
                      if (-1 == close(fmd->fd)) {
                          logg("!ClamFanotif: error occurred while closing metadata fd, %d\n", fmd->fd);
                          if (errno == EBADF) {
                              logg("ClamFanotif: fd already closed ... recovering ...\n");
                          } else {
                              return 2;
                          }
                      }
                  }
              }
              fmd = FAN_EVENT_NEXT(fmd, bread);
          }
          do {
              ret = select((*ctx)->fan_fd + 1, &rfds, NULL, NULL, NULL);
          } while ((ret == -1 && errno == EINTR));
      }
  
      if (bread < 0) {
          logg("!ClamFanotif: internal error (failed to read data) ... %s\n", strerror(errno));
          return 2;
      }
  
      return ret;
  }
  #endif
  