de1271a1 |
/* |
c442ca9c |
* Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2011-2013 Sourcefire, Inc. |
de1271a1 |
* |
5ae59b95 |
* Authors: Tomasz Kojm, Mickey Sola |
de1271a1 |
*
* 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
|
5ae59b95 |
#if defined(FANOTIFY) |
de1271a1 |
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include <errno.h> |
67e36ea7 |
#include <time.h> |
de1271a1 |
|
c50982c4 |
#include <sys/fanotify.h> |
b842e8bf |
|
de1271a1 |
#include "libclamav/clamav.h"
#include "libclamav/scanners.h"
#include "shared/optparser.h"
#include "shared/output.h"
|
51b69a09 |
#include "onaccess_others.h" |
de1271a1 |
#include "server.h"
|
5ae59b95 |
#include "onaccess_fan.h"
#include "onaccess_hash.h"
#include "onaccess_ddd.h"
static pthread_t ddd_pid; |
abbe4c4b |
static int onas_fan_fd; |
5ae59b95 |
static void onas_fan_exit(int sig) |
de1271a1 |
{ |
5ae59b95 |
logg("*ScanOnAccess: onas_fan_exit(), signal %d\n", sig); |
abbe4c4b |
close(onas_fan_fd); |
e0056fe6 |
if (ddd_pid > 0) {
pthread_kill(ddd_pid, SIGUSR1);
pthread_join(ddd_pid, NULL);
} |
5ae59b95 |
pthread_exit(NULL);
logg("ScanOnAccess: stopped\n"); |
de1271a1 |
}
|
5ae59b95 |
static int onas_fan_scanfile(int fan_fd, const char *fname, struct fanotify_event_metadata *fmd, int scan, int extinfo, struct thrarg *tharg) |
de1271a1 |
{
struct fanotify_response res; |
e551468a |
const char *virname = NULL; |
de1271a1 |
int ret = 0;
res.fd = fmd->fd;
res.response = FAN_ALLOW; |
b71960cd |
|
51b69a09 |
if (scan) {
if (onas_scan(fname, fmd->fd, &virname, tharg->engine, tharg->options, extinfo) == CL_VIRUS) {
/* TODO : FIXME? virusaction forks. This could be extraordinarily problematic, lead to deadlocks,
* or at the very least lead to extreme memory consumption. Leaving disabled for now.*/ |
b71960cd |
//virusaction(fname, virname, tharg->opts); |
51b69a09 |
res.response = FAN_DENY;
} |
de1271a1 |
}
if(fmd->mask & FAN_ALL_PERM_EVENTS) {
ret = write(fan_fd, &res, sizeof(res));
if(ret == -1)
logg("!ScanOnAccess: Internal error (can't write to fanotify)\n");
}
return ret;
}
|
5ae59b95 |
void *onas_fan_th(void *arg) |
de1271a1 |
{
struct thrarg *tharg = (struct thrarg *) arg;
sigset_t sigset;
struct sigaction act;
const struct optstruct *pt;
short int scan; |
6412bda7 |
unsigned int sizelimit = 0, extinfo; |
a2a004df |
STATBUF sb; |
400b4777 |
uint64_t fan_mask = FAN_EVENT_ON_CHILD | FAN_CLOSE; |
de1271a1 |
fd_set rfds;
char buf[4096];
ssize_t bread;
struct fanotify_event_metadata *fmd;
char fname[1024]; |
b71960cd |
int ret, len, check; |
de1271a1 |
char err[128];
|
5ae59b95 |
pthread_attr_t ddd_attr;
struct ddd_thrarg *ddd_tharg = NULL;
|
e0056fe6 |
ddd_pid = 0;
|
de1271a1 |
/* 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)); |
5ae59b95 |
act.sa_handler = onas_fan_exit; |
de1271a1 |
sigfillset(&(act.sa_mask));
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGSEGV, &act, NULL);
|
5ae59b95 |
/* Initialize fanotify */ |
2b122eb2 |
onas_fan_fd = fanotify_init(FAN_CLASS_CONTENT | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS, O_LARGEFILE | O_RDONLY); |
abbe4c4b |
if(onas_fan_fd < 0) { |
de1271a1 |
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;
}
|
4e5ebc3e |
if (!tharg) {
logg("!Unable to start on-access scanner. Bad thread args.\n");
return NULL;
}
|
cf703fa1 |
if (optget(tharg->opts, "OnAccessPrevention")->enabled && !optget(tharg->opts, "OnAccessMountPath")->enabled) { |
f408e5b2 |
logg("ScanOnAccess: preventing access attempts on malicious files.\n");
fan_mask |= FAN_ACCESS_PERM | FAN_OPEN_PERM; |
bbb244d9 |
} else { |
f408e5b2 |
logg("ScanOnAccess: notifying only for access attempts.\n");
fan_mask |= FAN_ACCESS | FAN_OPEN; |
bbb244d9 |
}
|
2217edf5 |
if ((pt = optget(tharg->opts, "OnAccessMountPath"))->enabled) {
while(pt) { |
f408e5b2 |
if(fanotify_mark(onas_fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, fan_mask, onas_fan_fd, pt->strarg) != 0) { |
2217edf5 |
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) { |
d7979d4f |
int thread_started = 1; |
4a1bd5c8 |
do {
if(pthread_attr_init(&ddd_attr)) break;
pthread_attr_setdetachstate(&ddd_attr, PTHREAD_CREATE_JOINABLE);
|
d7979d4f |
/* Allocate memory for arguments. Thread is responsible for freeing it. */
if (!(ddd_tharg = (struct ddd_thrarg *) calloc(sizeof(struct ddd_thrarg), 1))) break;
if (!(ddd_tharg->options = (struct cl_scan_options *) calloc(sizeof(struct cl_scan_options), 1))) break; |
4a1bd5c8 |
|
d7979d4f |
(void) memcpy(ddd_tharg->options, tharg->options, sizeof(struct cl_scan_options)); |
4a1bd5c8 |
ddd_tharg->fan_fd = onas_fan_fd;
ddd_tharg->fan_mask = fan_mask;
ddd_tharg->opts = tharg->opts;
ddd_tharg->engine = tharg->engine;
|
d7979d4f |
thread_started = pthread_create(&ddd_pid, &ddd_attr, onas_ddd_th, ddd_tharg); |
4a1bd5c8 |
} while(0); |
d7979d4f |
if (0 != thread_started) {
/* Failed to create thread. Free anything we may have allocated. */
logg("!Unable to start dynamic directory determination.\n");
if (NULL != ddd_tharg) {
if (NULL != ddd_tharg->options) {
free(ddd_tharg->options);
ddd_tharg->options = NULL;
}
free(ddd_tharg);
ddd_tharg = NULL;
}
} |
4a1bd5c8 |
} 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;
}
} |
de1271a1 |
|
5ae59b95 |
/* Load other options. */ |
de1271a1 |
sizelimit = optget(tharg->opts, "OnAccessMaxFileSize")->numarg;
if(sizelimit) |
6412bda7 |
logg("ScanOnAccess: Max file size limited to %u bytes\n", sizelimit); |
de1271a1 |
else
logg("ScanOnAccess: File size limit disabled\n");
extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled;
FD_ZERO(&rfds); |
abbe4c4b |
FD_SET(onas_fan_fd, &rfds); |
de1271a1 |
do { |
405ec90b |
if (reload) sleep(1); |
abbe4c4b |
ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL); |
0c5992d3 |
} while((ret == -1 && errno == EINTR) || reload); |
de1271a1 |
|
67e36ea7 |
time_t start = time(NULL) - 30; |
2b122eb2 |
while(((bread = read(onas_fan_fd, buf, sizeof(buf))) > 0) || errno == EOVERFLOW) {
if (errno == EOVERFLOW) { |
67e36ea7 |
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);
}
|
2b122eb2 |
errno = 0;
continue;
}
|
de1271a1 |
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;
|
b71960cd |
if((check = onas_fan_checkowner(fmd->pid, tharg->opts))) { |
de1271a1 |
scan = 0; |
3d9620db |
/* TODO: Re-enable OnAccessExtraScanning once the thread resource consumption issue is resolved. */
#if 0
if ((check != CHK_SELF) || !(optget(tharg->opts, "OnAccessExtraScanning")->enabled)) {
#else
if (check != CHK_SELF) {
#endif
logg("*ScanOnAccess: %s skipped (excluded UID)\n", fname);
} |
de1271a1 |
}
if(sizelimit) { |
b377f2f6 |
if(FSTAT(fmd->fd, &sb) != 0 || sb.st_size > sizelimit) { |
de1271a1 |
scan = 0;
/* logg("*ScanOnAccess: %s skipped (size > %d)\n", fname, sizelimit); */
}
}
|
abbe4c4b |
if(onas_fan_scanfile(onas_fan_fd, fname, fmd, scan, extinfo, tharg) == -1) { |
de1271a1 |
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 { |
405ec90b |
if (reload) sleep(1); |
abbe4c4b |
ret = select(onas_fan_fd + 1, &rfds, NULL, NULL, NULL); |
0c5992d3 |
} while((ret == -1 && errno == EINTR) || reload); |
de1271a1 |
}
if(bread < 0) |
2b122eb2 |
logg("!ScanOnAccess: Internal error (failed to read data) ... %s\n", strerror(errno)); |
de1271a1 |
return NULL;
}
|
51b69a09 |
/* CLAMAUTH is deprecated */ |
d68aba83 |
#elif defined(CLAMAUTH)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include "libclamav/clamav.h"
#include "libclamav/scanners.h"
#include "shared/optparser.h"
#include "shared/output.h"
#include "server.h"
#include "others.h"
#include "scanner.h"
#define SUPPORTED_PROTOCOL 2
|
97efd6f8 |
static int cauth_fd = -1;
|
d68aba83 |
struct ClamAuthEvent {
unsigned int action;
char path[1024];
unsigned int pid;
};
static void cauth_exit(int sig)
{
logg("*ScanOnAccess: cauth_exit(), signal %d\n", sig); |
97efd6f8 |
if(cauth_fd > 0)
close(cauth_fd); |
d68aba83 |
pthread_exit(NULL);
logg("ScanOnAccess: stopped\n");
}
static int cauth_scanfile(const char *fname, int extinfo, struct thrarg *tharg)
{
struct cb_context context; |
e551468a |
const char *virname = NULL; |
d68aba83 |
int ret = 0, fd;
context.filename = fname;
context.virsize = 0; |
bd6ce1e4 |
context.scandata = NULL; |
d68aba83 |
fd = open(fname, O_RDONLY);
if(fd == -1)
return -1;
|
d39cb658 |
if(cl_scandesc_callback(fd, fname, &virname, NULL, tharg->engine, tharg->options, &context) == CL_VIRUS) { |
d68aba83 |
if(extinfo && context.virsize)
logg("ScanOnAccess: %s: %s(%s:%llu) FOUND\n", fname, virname, context.virhash, context.virsize);
else
logg("ScanOnAccess: %s: %s FOUND\n", fname, virname);
virusaction(fname, virname, tharg->opts);
}
close(fd);
return ret;
}
|
5ae59b95 |
void *onas_fan_th(void *arg) |
d68aba83 |
{
struct thrarg *tharg = (struct thrarg *) arg;
sigset_t sigset;
struct sigaction act; |
97efd6f8 |
int eventcnt = 1, extinfo; |
d68aba83 |
char err[128];
struct ClamAuthEvent event;
/* 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 = cauth_exit;
sigfillset(&(act.sa_mask));
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGSEGV, &act, NULL);
extinfo = optget(tharg->opts, "ExtendedDetectionInfo")->enabled;
|
97efd6f8 |
cauth_fd = open("/dev/clamauth", O_RDONLY);
if(cauth_fd == -1) { |
d68aba83 |
logg("!ScanOnAccess: Can't open /dev/clamauth\n");
if(errno == ENOENT)
logg("!ScanOnAccess: Please make sure ClamAuth.kext is loaded\n");
else if(errno == EACCES)
logg("!ScanOnAccess: This application requires root privileges\n");
else
logg("!ScanOnAccess: /dev/clamauth: %s\n", cli_strerror(errno, err, sizeof(err)));
return NULL;
}
while(1) { |
97efd6f8 |
if(read(cauth_fd, &event, sizeof(event)) > 0) { |
d68aba83 |
if(eventcnt == 1) {
if(event.action != SUPPORTED_PROTOCOL) {
logg("!ScanOnAccess: Protocol version mismatch (tool: %d, driver: %d)\n", SUPPORTED_PROTOCOL, event.action); |
97efd6f8 |
close(cauth_fd); |
d68aba83 |
return NULL;
}
if(strncmp(event.path, "ClamAuth", 8)) {
logg("!ScanOnAccess: Invalid version event\n"); |
97efd6f8 |
close(cauth_fd); |
d68aba83 |
return NULL;
}
logg("ScanOnAccess: Driver version: %s, protocol version: %d\n", &event.path[9], event.action);
} else {
cauth_scanfile(event.path, extinfo, tharg);
}
eventcnt++;
} else {
if(errno == ENODEV) {
printf("^ScanOnAccess: ClamAuth module deactivated, terminating\n"); |
97efd6f8 |
close(cauth_fd); |
d68aba83 |
return NULL;
}
}
usleep(200);
}
} |
de1271a1 |
#endif |