mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
10 KiB
284 lines
10 KiB
/*
|
|
* 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(HAVE_SYS_FANOTIFY_H)
|
|
|
|
#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>
|
|
|
|
// libclamav
|
|
#include "clamav.h"
|
|
#include "scanners.h"
|
|
|
|
// shared
|
|
#include "optparser.h"
|
|
#include "output.h"
|
|
|
|
// clamd
|
|
#include "server.h"
|
|
|
|
#include "../inotif/hash.h"
|
|
#include "../inotif/inotif.h"
|
|
|
|
#include "../client/client.h"
|
|
|
|
#include "../scan/thread.h"
|
|
#include "../scan/onas_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
|
|
|