/* * Copyright (C) 2019 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; short int scan; unsigned int sizelimit = 0, extinfo; uint64_t fan_mask = FAN_EVENT_ON_CHILD; pthread_attr_t ddd_attr; struct ddd_thrarg *ddd_tharg = NULL; 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"); } extinfo = optget((*ctx)->clamdopts, "ExtendedDetectionInfo")->enabled; return CL_SUCCESS; } int onas_fan_eloop(struct onas_context **ctx) { int ret = 0; int err_cnt = 0; short int scan; STATBUF sb; fd_set rfds; char buf[4096]; ssize_t bread; struct fanotify_event_metadata *fmd; char fname[1024]; int len, check, fres; char err[128]; 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